Паттерны использования
Стандартный обход коллекций
Работа с вложенными коллекциями
Обход массивов
Индексированный цикл for: полный контроль над итерацией
Концептуальные основы
Индексированный цикл for предоставляет полный контроль над процессом итерации, включая управление индексом, направлением обхода и возможностью пропуска элементов.
Синтаксис и структура
Механизм выполнения
Фазы выполнения
Инициализация: Выполняется один раз перед началом цикла
Проверка условия: Выполняется перед каждой итерацией
Выполнение тела: Если условие истинно
Инкремент: Выполняется после каждой итерации
Возврат к шагу 2
Байт-код представление
Варианты использования
Стандартный последовательный обход
Обратный обход
Обход с шагом
Множественные переменные цикла
Производительность и оптимизации
Для ArrayList:
Производительность:
For-each: Создание итератора + вызовы hasNext()/next()
Индексированный for: Прямой доступ по индексу + boundary checking
Для LinkedList:
Производительность:
For-each: O(n) общее время
Индексированный for: O(n²) из-за последовательного поиска элементов
Особые техники и паттерны
Пакетная обработка
Обход с модификацией
Параллельная обработка индексов
#Java #для_новичков #beginner #for_each #fail_fast #fail_safe
Стандартный обход коллекций
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Явное использование итератора
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
System.out.println(name);
}
// Эквивалентный for-each
for (String name : names) {
System.out.println(name);
}Работа с вложенными коллекциями
List<List<Integer>> matrix = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList(7, 8, 9)
);
// Вложенные for-each
for (List<Integer> row : matrix) {
for (Integer value : row) {
System.out.print(value + " ");
}
System.out.println();
}
Обход массивов
int[] numbers = {1, 2, 3, 4, 5};
// Традиционный for
for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers[i]);
}
// For-each
for (int number : numbers) {
System.out.println(number);
}Индексированный цикл for: полный контроль над итерацией
Концептуальные основы
Индексированный цикл for предоставляет полный контроль над процессом итерации, включая управление индексом, направлением обхода и возможностью пропуска элементов.
Синтаксис и структура
for (инициализация; условие; инкремент) {
// Тело цикла
}Механизм выполнения
Фазы выполнения
Инициализация: Выполняется один раз перед началом цикла
Проверка условия: Выполняется перед каждой итерацией
Выполнение тела: Если условие истинно
Инкремент: Выполняется после каждой итерации
Возврат к шагу 2
Байт-код представление
// Псевдокод байт-кода
ICONST_0
ISTORE index
GOTO condition
loop:
// Тело цикла
ILOAD index
ICONST_1
IADD
ISTORE index
condition:
ILOAD index
array_length
IF_ICMPLT loop
Варианты использования
Стандартный последовательный обход
List<String> list = new ArrayList<>();
// ... инициализация
for (int i = 0; i < list.size(); i++) {
String element = list.get(i);
process(element);
}
Обратный обход
for (int i = list.size() - 1; i >= 0; i--) {
String element = list.get(i);
process(element);
}Обход с шагом
for (int i = 0; i < list.size(); i += 2) {
String element = list.get(i);
process(element);
}Множественные переменные цикла
for (int i = 0, j = 10; i < 10 && j > 0; i++, j--) {
System.out.println("i=" + i + ", j=" + j);
}Производительность и оптимизации
Для ArrayList:
// For-each (опосредованно через итератор)
for (String item : arrayList) {
process(item);
}
// Индексированный for
for (int i = 0; i < arrayList.size(); i++) {
String item = arrayList.get(i);
process(item);
}
Производительность:
For-each: Создание итератора + вызовы hasNext()/next()
Индексированный for: Прямой доступ по индексу + boundary checking
Для LinkedList:
// For-each (эффективно)
for (String item : linkedList) {
process(item);
}
// Индексированный for (неэффективно!)
for (int i = 0; i < linkedList.size(); i++) {
String item = linkedList.get(i); // O(n) для каждого вызова!
process(item);
}
Производительность:
For-each: O(n) общее время
Индексированный for: O(n²) из-за последовательного поиска элементов
Особые техники и паттерны
Пакетная обработка
int batchSize = 50;
for (int i = 0; i < data.size(); i += batchSize) {
int end = Math.min(i + batchSize, data.size());
List<Item> batch = data.subList(i, end);
processBatch(batch);
}
Обход с модификацией
// Удаление элементов во время обхода (только для ArrayList)
for (int i = 0; i < list.size(); i++) {
if (shouldRemove(list.get(i))) {
list.remove(i);
i--; // Корректировка индекса после удаления
}
}
Параллельная обработка индексов
// Обработка двух коллекций одновременно
for (int i = 0; i < Math.min(list1.size(), list2.size()); i++) {
processPair(list1.get(i), list2.get(i));
}
#Java #для_новичков #beginner #for_each #fail_fast #fail_safe
👍2
Рекомендации по выбору
Когда использовать for-each:
Простой последовательный обход без необходимости модификации
Работа с LinkedList или другими не-RandomAccess коллекциями
Улучшение читаемости кода
Обход массивов когда индекс не нужен
Работа с вложенными структурами
Когда использовать индексированный for:
Необходимость доступа к индексу
Обратный или нестандартный обход
Модификация коллекции во время итерации
Пакетная обработка с определенным шагом
Работа с ArrayList или массивами
Гибридные подходы
Использование счетчика с for-each
Обход с ListIterator
Fail-fast коллекции: философия быстрого отказа
Концепция fail-fast
Fail-fast (быстрый отказ) — это подход к обработке ошибок, при котором система немедленно сообщает о проблеме, как только она обнаружена. В контексте коллекций Java это означает, что итераторы обнаруживают структурные модификации коллекции во время итерации и немедленно выбрасывают исключение.
Механизм реализации
Ключевым элементом fail-fast поведения является поле modCount:
Назначение: Отслеживание количества структурных модификаций коллекции.
Инкрементация: Увеличивается при каждой операции, изменяющей размер или структуру коллекции.
Использование: Сравнивается с сохраненной копией в итераторе.
Реализация в итераторах
Коллекции с fail-fast семантикой
ArrayList и все его производные
LinkedList
HashSet
HashMap (итераторы entrySet, keySet, values)
TreeMap и TreeSet
Пример поведения
Детальный анализ исключения ConcurrentModificationException
Условия возникновения
Структурная модификация: Изменение размера коллекции (add, remove)
Одновременная итерация: Активный итератор или for-each цикл
Разные объекты: Модификация не через текущий итератор
Что считается структурной модификацией
Вызывают исключение:
add(), addAll()
remove(), removeAll(), retainAll(), clear()
set() на определенных позициях в некоторых реализациях
Не вызывают исключение:
set() для замены элемента без изменения размера
Операции через Iterator.remove()
Операции через ListIterator.add(), set(), remove()
Механизм обнаружения изменений
#Java #для_новичков #beginner #for_each #fail_fast #fail_safe
Когда использовать for-each:
Простой последовательный обход без необходимости модификации
Работа с LinkedList или другими не-RandomAccess коллекциями
Улучшение читаемости кода
Обход массивов когда индекс не нужен
Работа с вложенными структурами
// Идеальный сценарий для for-each
for (Customer customer : customers) {
sendNotification(customer);
}
Когда использовать индексированный for:
Необходимость доступа к индексу
Обратный или нестандартный обход
Модификация коллекции во время итерации
Пакетная обработка с определенным шагом
Работа с ArrayList или массивами
// Идеальный сценарий для индексированного for
for (int i = 0; i < array.length; i++) {
if (array[i] == null) {
array[i] = defaultValue;
}
}
Гибридные подходы
Использование счетчика с for-each
int index = 0;
for (String item : collection) {
processWithIndex(item, index);
index++;
}
Обход с ListIterator
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
String item = iterator.next();
int index = iterator.previousIndex();
processWithIndex(item, index);
}
Fail-fast коллекции: философия быстрого отказа
Концепция fail-fast
Fail-fast (быстрый отказ) — это подход к обработке ошибок, при котором система немедленно сообщает о проблеме, как только она обнаружена. В контексте коллекций Java это означает, что итераторы обнаруживают структурные модификации коллекции во время итерации и немедленно выбрасывают исключение.
Механизм реализации
Ключевым элементом fail-fast поведения является поле modCount:
// Пример из AbstractList
protected transient int modCount = 0;
Назначение: Отслеживание количества структурных модификаций коллекции.
Инкрементация: Увеличивается при каждой операции, изменяющей размер или структуру коллекции.
Использование: Сравнивается с сохраненной копией в итераторе.
Реализация в итераторах
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
int cursor = 0;
public E next() {
checkForComodification(); // Проверка изменений
// ... остальная логика
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
public void remove() {
checkForComodification();
// ... удаление элемента
expectedModCount = modCount; // Синхронизация после успешного удаления
}
}Коллекции с fail-fast семантикой
ArrayList и все его производные
LinkedList
HashSet
HashMap (итераторы entrySet, keySet, values)
TreeMap и TreeSet
Пример поведения
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
// Попытка модификации во время итерации
for (String item : list) {
if (item.equals("B")) {
list.remove(item); // ConcurrentModificationException!
}
}Детальный анализ исключения ConcurrentModificationException
Условия возникновения
Структурная модификация: Изменение размера коллекции (add, remove)
Одновременная итерация: Активный итератор или for-each цикл
Разные объекты: Модификация не через текущий итератор
Что считается структурной модификацией
Вызывают исключение:
add(), addAll()
remove(), removeAll(), retainAll(), clear()
set() на определенных позициях в некоторых реализациях
Не вызывают исключение:
set() для замены элемента без изменения размера
Операции через Iterator.remove()
Операции через ListIterator.add(), set(), remove()
Механизм обнаружения изменений
// Упрощенная проверка в ArrayList.Itr
final void checkForComodification() {
if (modCount != expectedModCount) {
// Разные стратегии в разных коллекциях
if (modCount == expectedModCount + 1 && lastRet < 0) {
// Возможно, это наш собственный remove()
expectedModCount = modCount;
} else {
throw new ConcurrentModificationException();
}
}
}
#Java #для_новичков #beginner #for_each #fail_fast #fail_safe
👍2
Стратегии работы с fail-fast коллекциями
Безопасные паттерны
Сбор элементов для удаления:
Использование индексированного цикла:
Опасные паттерны (антипаттерны)
Модификация через коллекцию во время итерации:
Параллельные модификации из разных потоков:
Преимущества и недостатки fail-fast подхода
Преимущества
Раннее обнаружение ошибок: Проблемы выявляются сразу же
Предсказуемость: Гарантированная consistency во время итерации
Простота отладки: Точное указание на проблему
Защита от тонких багов: Предотвращение незаметной порчи данных
Недостатки
Ограничения на модификации: Нельзя изменять коллекцию во время итерации
Производительность: Дополнительные проверки на каждой операции
Сложность в многопоточных сценариях: Требует внешней синхронизации
Fail-safe коллекции: философия безопасного продолжения
Fail-safe (безопасный отказ) — это подход, при котором система продолжает работу даже при возникновении ошибок или изменений состояния. В контексте коллекций это означает, что итераторы работают с моментальным снимком (snapshot) данных или используют специальные механизмы для обеспечения consistency во время итерации.
Механизмы реализации
Copy-on-write (копирование при записи)
Наиболее распространенный механизм fail-safe:
Слабая согласованность (weakly consistent)
Альтернативный подход, используемый в concurrent коллекциях:
Итератор видит элементы, существовавшие на момент его создания
Может видеть некоторые, но не все последующие изменения
Не выбрасывает ConcurrentModificationException
Коллекции с fail-safe семантикой
CopyOnWriteArrayList
Принцип работы: При каждой модификации создается новая копия внутреннего массива.
ConcurrentHashMap
Принцип работы: Сегментированная блокировка и слабо-консистентные итераторы.
ConcurrentLinkedQueue
Принцип работы: Lock-free алгоритмы и weak consistency.
#Java #для_новичков #beginner #for_each #fail_fast #fail_safe
Безопасные паттерны
Использование Iterator.remove():
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (shouldRemove(item)) {
iterator.remove(); // Безопасно!
}
}
Сбор элементов для удаления:
List<String> toRemove = new ArrayList<>();
for (String item : list) {
if (shouldRemove(item)) {
toRemove.add(item);
}
}
list.removeAll(toRemove); // Безопасно вне цикла
Использование индексированного цикла:
for (int i = 0; i < list.size(); i++) {
if (shouldRemove(list.get(i))) {
list.remove(i);
i--; // Корректировка индекса
}
}Опасные паттерны (антипаттерны)
Модификация через коллекцию во время итерации:
for (String item : list) {
list.add("new"); // ConcurrentModificationException!
}Параллельные модификации из разных потоков:
// Поток 1
for (String item : list) {
// Итерация
}
// Поток 2
list.add("new"); // ConcurrentModificationException в потоке 1
Преимущества и недостатки fail-fast подхода
Преимущества
Раннее обнаружение ошибок: Проблемы выявляются сразу же
Предсказуемость: Гарантированная consistency во время итерации
Простота отладки: Точное указание на проблему
Защита от тонких багов: Предотвращение незаметной порчи данных
Недостатки
Ограничения на модификации: Нельзя изменять коллекцию во время итерации
Производительность: Дополнительные проверки на каждой операции
Сложность в многопоточных сценариях: Требует внешней синхронизации
Fail-safe коллекции: философия безопасного продолжения
Fail-safe (безопасный отказ) — это подход, при котором система продолжает работу даже при возникновении ошибок или изменений состояния. В контексте коллекций это означает, что итераторы работают с моментальным снимком (snapshot) данных или используют специальные механизмы для обеспечения consistency во время итерации.
Механизмы реализации
Copy-on-write (копирование при записи)
Наиболее распространенный механизм fail-safe:
public class CopyOnWriteArrayList<E> {
private transient volatile Object[] array;
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
static final class COWIterator<E> implements ListIterator<E> {
private final Object[] snapshot;
private int cursor;
COWIterator(Object[] elements, int initialCursor) {
snapshot = elements; // Снимок на момент создания
cursor = initialCursor;
}
public E next() {
if (!hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
}
}Слабая согласованность (weakly consistent)
Альтернативный подход, используемый в concurrent коллекциях:
Итератор видит элементы, существовавшие на момент его создания
Может видеть некоторые, но не все последующие изменения
Не выбрасывает ConcurrentModificationException
Коллекции с fail-safe семантикой
CopyOnWriteArrayList
Принцип работы: При каждой модификации создается новая копия внутреннего массива.
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// Безопасная модификация во время итерации
for (String item : list) { // Итератор использует snapshot
if (condition(item)) {
list.add("new"); // Не влияет на текущую итерацию
}
}
ConcurrentHashMap
Принцип работы: Сегментированная блокировка и слабо-консистентные итераторы.
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// Итерация по entrySet
for (Map.Entry<String, Integer> entry : map.entrySet()) {
map.put("newKey", 42); // Безопасно, но может не отобразиться в текущем итераторе
}
ConcurrentLinkedQueue
Принцип работы: Lock-free алгоритмы и weak consistency.
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
for (String item : queue) {
queue.offer("new"); // Безопасно
}
#Java #для_новичков #beginner #for_each #fail_fast #fail_safe
👍2
Детальный анализ поведения fail-safe коллекций
CopyOnWriteArrayList: детали реализации
Структура данных:
Алгоритм добавления элемента:
Характеристики:
Чтение: O(1), без блокировок
Запись: O(n), требует полного копирования
Память: Дополнительная копия во время модификации
Итераторы: Всегда работают с snapshot
ConcurrentHashMap: сегментированная архитектура
Структура (Java 7 и ранее):
Итерация:
Характеристики:
Чтение: O(1) в среднем, без блокировок
Запись: O(1) в среднем, блокировка только сегмента
Итераторы: Weakly consistent, могут не видеть все изменения
Преимущества и недостатки fail-safe подхода
Преимущества
Безопасность в многопоточных сценариях: Нет необходимости во внешней синхронизации
Отсутствие ConcurrentModificationException: Итерация никогда не прерывается
Высокая производительность чтения: Особенно в read-heavy workload'ах
Простота использования: Меньше требований к синхронизации
Недостатки
Расход памяти: Copy-on-write требует дополнительной памяти
Задержка видимости изменений: Итераторы могут не видеть свежие данные
Производительность записи: Copy-on-write имеет высокую стоимость
Сложность семантики: Weak consistency может быть неинтуитивной
Паттерны использования fail-safe коллекций
Сценарии для CopyOnWriteArrayList
Конфигурационные данные:
Кэширование часто читаемых данных:
Сценарии для ConcurrentHashMap
Кэш с конкурентным доступом:
#Java #для_новичков #beginner #for_each #fail_fast #fail_safe
CopyOnWriteArrayList: детали реализации
Структура данных:
public class CopyOnWriteArrayList<E> {
// volatile для обеспечения memory visibility
private transient volatile Object[] array;
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
}Алгоритм добавления элемента:
public boolean add(E e) {
synchronized(lock) {
Object[] elements = getArray();
int len = elements.length;
// Создание новой копии
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
// Атомарная замена ссылки
setArray(newElements);
return true;
}
}Характеристики:
Чтение: O(1), без блокировок
Запись: O(n), требует полного копирования
Память: Дополнительная копия во время модификации
Итераторы: Всегда работают с snapshot
ConcurrentHashMap: сегментированная архитектура
Структура (Java 7 и ранее):
public class ConcurrentHashMap<K, V> {
final Segment<K,V>[] segments; // Массив сегментов
static final class Segment<K,V> extends ReentrantLock {
transient volatile HashEntry<K,V>[] table;
}
}Итерация:
public Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
// Weakly consistent итератор
final class EntryIterator extends HashIterator
implements Iterator<Entry<K,V>> {
public Map.Entry<K,V> next() {
HashEntry<K,V> e = super.nextEntry();
return new WriteThroughEntry(e.key, e.value);
}
}Характеристики:
Чтение: O(1) в среднем, без блокировок
Запись: O(1) в среднем, блокировка только сегмента
Итераторы: Weakly consistent, могут не видеть все изменения
Преимущества и недостатки fail-safe подхода
Преимущества
Безопасность в многопоточных сценариях: Нет необходимости во внешней синхронизации
Отсутствие ConcurrentModificationException: Итерация никогда не прерывается
Высокая производительность чтения: Особенно в read-heavy workload'ах
Простота использования: Меньше требований к синхронизации
Недостатки
Расход памяти: Copy-on-write требует дополнительной памяти
Задержка видимости изменений: Итераторы могут не видеть свежие данные
Производительность записи: Copy-on-write имеет высокую стоимость
Сложность семантики: Weak consistency может быть неинтуитивной
Паттерны использования fail-safe коллекций
Сценарии для CopyOnWriteArrayList
Конфигурационные данные:
public class ConfigurationManager {
private final CopyOnWriteArrayList<Listener> listeners =
new CopyOnWriteArrayList<>();
public void addListener(Listener listener) {
listeners.add(listener); // Безопасно, даже во время уведомлений
}
public void notifyListeners() {
for (Listener listener : listeners) { // Snapshot итератор
listener.onChange();
}
}
}Кэширование часто читаемых данных:
public class DataCache {
private volatile CopyOnWriteArrayList<Data> cache =
new CopyOnWriteArrayList<>();
public void refreshCache() {
List<Data> newData = loadData();
cache = new CopyOnWriteArrayList<>(newData); // Атомарная замена
}
public List<Data> getData() {
return cache; // Безопасное чтение
}
}Сценарии для ConcurrentHashMap
Кэш с конкурентным доступом:
public class ConcurrentCache<K, V> {
private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
public V get(K key) {
return cache.get(key); // Без блокировок
}
public V computeIfAbsent(K key, Function<K, V> loader) {
return cache.computeIfAbsent(key, loader); // Атомарно
}
public void processAll() {
for (Map.Entry<K, V> entry : cache.entrySet()) {
// Безопасная итерация даже при concurrent модификациях
processEntry(entry);
}
}
}#Java #для_новичков #beginner #for_each #fail_fast #fail_safe
👍2
Критерии выбора fail-fast и fail-safe
Выбор fail-fast коллекций когда:
Single-threaded окружение или контролируемая многопоточность
Важна немедленная видимость изменений
Ограниченные ресурсы памяти
Частые операции записи
Нужна предсказуемость поведения
Выбор fail-safe коллекций когда:
Высокая конкурентность чтения и записи
Read-heavy workloads
Не критична задержка видимости изменений
Достаточно памяти для copy-on-write
Нужна thread-safe семантика без внешней синхронизации
Гибридные подходы
Комбинация fail-fast и внешней синхронизации
Использование ReadWriteLock
Практические рекомендации и best practices
Общие рекомендации по выбору циклов
Предпочитайте for-each для простого последовательного обхода
Используйте индексированный for когда нужен индекс или нестандартный обход
Избегайте индексированного for для LinkedList
Рассмотрите Stream API для сложных операций фильтрации и трансформации
Рекомендации по работе с fail-fast коллекциями
Не модифицируйте коллекцию во время итерации через методы коллекции
Используйте Iterator.remove() для безопасного удаления
Собирайте изменения отдельно для пакетного применения
Синхронизируйте доступ в многопоточных сценариях
Рекомендации по работе с fail-safe коллекциями
Понимайте семантику weak consistency для concurrent коллекций
Учитывайте стоимость copy-on-write для больших коллекций
Используйте для read-heavy workloads
Избегайте частых модификаций больших CopyOnWriteArrayList
#Java #для_новичков #beginner #for_each #fail_fast #fail_safe
Выбор fail-fast коллекций когда:
Single-threaded окружение или контролируемая многопоточность
Важна немедленная видимость изменений
Ограниченные ресурсы памяти
Частые операции записи
Нужна предсказуемость поведения
// Оптимально для single-threaded сценариев
List<String> singleThreadedList = new ArrayList<>();
Выбор fail-safe коллекций когда:
Высокая конкурентность чтения и записи
Read-heavy workloads
Не критична задержка видимости изменений
Достаточно памяти для copy-on-write
Нужна thread-safe семантика без внешней синхронизации
// Оптимально для многопоточных read-heavy сценариев
List<String> concurrentList = new CopyOnWriteArrayList<>();
Гибридные подходы
Комбинация fail-fast и внешней синхронизации
public class SynchronizedListWrapper {
private final List<String> list = new ArrayList<>();
public synchronized void safeIteration() {
// Внутри synchronized блока fail-fast безопасен
for (String item : list) {
process(item);
}
}
public synchronized void modify() {
list.add("new");
}
}Использование ReadWriteLock
public class ReadWriteProtectedList {
private final List<String> list = new ArrayList<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public void iterateSafely() {
lock.readLock().lock();
try {
for (String item : list) {
process(item); // Безопасное чтение
}
} finally {
lock.readLock().unlock();
}
}
public void modifySafely() {
lock.writeLock().lock();
try {
list.add("new"); // Безопасная модификация
} finally {
lock.writeLock().unlock();
}
}
}Практические рекомендации и best practices
Общие рекомендации по выбору циклов
Предпочитайте for-each для простого последовательного обхода
Используйте индексированный for когда нужен индекс или нестандартный обход
Избегайте индексированного for для LinkedList
Рассмотрите Stream API для сложных операций фильтрации и трансформации
// For-each для простого обхода
for (Item item : items) {
process(item);
}
// Индексированный for когда нужен индекс
for (int i = 0; i < items.size(); i++) {
processWithIndex(items.get(i), i);
}
// Stream API для сложных операций
items.stream()
.filter(this::shouldProcess)
.map(this::transform)
.forEach(this::process);
Рекомендации по работе с fail-fast коллекциями
Не модифицируйте коллекцию во время итерации через методы коллекции
Используйте Iterator.remove() для безопасного удаления
Собирайте изменения отдельно для пакетного применения
Синхронизируйте доступ в многопоточных сценариях
// Безопасный паттерн
List<String> toRemove = new ArrayList<>();
for (String item : list) {
if (shouldRemove(item)) {
toRemove.add(item);
}
}
list.removeAll(toRemove);
Рекомендации по работе с fail-safe коллекциями
Понимайте семантику weak consistency для concurrent коллекций
Учитывайте стоимость copy-on-write для больших коллекций
Используйте для read-heavy workloads
Избегайте частых модификаций больших CopyOnWriteArrayList
// Оптимальное использование CopyOnWriteArrayList
CopyOnWriteArrayList<Data> dataCache = new CopyOnWriteArrayList<>();
// Редкое обновление всего кэша
public void refreshCache() {
List<Data> freshData = loadFreshData();
dataCache = new CopyOnWriteArrayList<>(freshData);
}
// Частое чтение
public List<Data> getCachedData() {
return dataCache;
}
#Java #для_новичков #beginner #for_each #fail_fast #fail_safe
👍2
Gateway Filters: Глубокая архитектура и реактивная реализация
Реактивная природа фильтров в Spring Cloud Gateway
В Spring Cloud Gateway каждый фильтр реализует интерфейс GatewayFilter, который определяет единственный метод:
Ключевой аспект — возвращаемый тип Mono<Void>. Это реактивный тип, представляющий отложенное вычисление, которое может завершиться успешно, с ошибкой или никогда не завершиться. Фильтры не блокируют поток выполнения (thread) — вместо этого они описывают pipeline обработки, который выполняется асинхронно.
Пример неблокирующего фильтра:
Цепочка фильтров как реактивный pipeline
GatewayFilterChain представляет собой последовательность фильтров, организованную как связанный список. Когда вызывается chain.filter(exchange), выполнение передаётся следующему фильтру в цепочке. Последний фильтр в цепочке вызывает фактическую маршрутизацию запроса к целевому сервису через NettyRoutingFilter.
Внутренняя реализация DefaultGatewayFilterChain:
Важное наблюдение: цепочка фильтров не выполняется немедленно. Она описывается как последовательность операторов реактивного программирования, которые будут выполнены позже, когда на них подпишутся.
Порядок выполнения фильтров
Фильтры выполняются в строго определённом порядке:
GlobalFilter с наименьшим значением getOrder() (отрицательные значения имеют наивысший приоритет)
Остальные GlobalFilter в порядке возрастания значения getOrder()
Фильтры маршрута в порядке их объявления в конфигурации
Пример конфликта порядка:
#Java #middle #Spring_Cloud_Gateway #Filters
Реактивная природа фильтров в Spring Cloud Gateway
В Spring Cloud Gateway каждый фильтр реализует интерфейс GatewayFilter, который определяет единственный метод:
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
Ключевой аспект — возвращаемый тип Mono<Void>. Это реактивный тип, представляющий отложенное вычисление, которое может завершиться успешно, с ошибкой или никогда не завершиться. Фильтры не блокируют поток выполнения (thread) — вместо этого они описывают pipeline обработки, который выполняется асинхронно.
Пример неблокирующего фильтра:
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Этот код выполняется синхронно при построении цепочки
long startTime = System.currentTimeMillis();
// Возвращаем Mono, который описывает асинхронную операцию
return chain.filter(exchange)
.then(Mono.fromRunnable(() -> {
// Этот код выполняется асинхронно ПОСЛЕ завершения цепочки
long duration = System.currentTimeMillis() - startTime;
log.info("Request processed in {} ms", duration);
}));
}Цепочка фильтров как реактивный pipeline
GatewayFilterChain представляет собой последовательность фильтров, организованную как связанный список. Когда вызывается chain.filter(exchange), выполнение передаётся следующему фильтру в цепочке. Последний фильтр в цепочке вызывает фактическую маршрутизацию запроса к целевому сервису через NettyRoutingFilter.
Внутренняя реализация DefaultGatewayFilterChain:
public class DefaultGatewayFilterChain implements GatewayFilterChain {
private final List<GatewayFilter> filters;
private final int index;
private final Publisher<Void> currentPublisher;
@Override
public Mono<Void> filter(ServerWebExchange exchange) {
return Mono.defer(() -> {
if (this.index < filters.size()) {
GatewayFilter filter = filters.get(this.index);
DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(
this, this.index + 1);
return filter.filter(exchange, chain);
} else {
return Mono.empty(); // Конец цепочки
}
});
}
}Важное наблюдение: цепочка фильтров не выполняется немедленно. Она описывается как последовательность операторов реактивного программирования, которые будут выполнены позже, когда на них подпишутся.
Порядок выполнения фильтров
Фильтры выполняются в строго определённом порядке:
GlobalFilter с наименьшим значением getOrder() (отрицательные значения имеют наивысший приоритет)
Остальные GlobalFilter в порядке возрастания значения getOrder()
Фильтры маршрута в порядке их объявления в конфигурации
Пример конфликта порядка:
@Component
@Order(-1) // Выполняется ПЕРВЫМ
public class FirstGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("First global filter");
return chain.filter(exchange);
}
}
@Component
@Order(0) // Выполняется ВТОРЫМ
public class SecondGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("Second global filter");
return chain.filter(exchange);
}
}
#Java #middle #Spring_Cloud_Gateway #Filters
👍1
Детальный разбор встроенных фильтров
AddRequestHeader / AddResponseHeader
Эти фильтры добавляют заголовки к запросу или ответу.
Важно понимать их реактивную природу:
Особенность: Заголовки добавляются в момент модификации запроса, который происходит синхронно при построении цепочки. Однако сама отправка модифицированного запроса к целевому сервису происходит асинхронно.
RemoveRequestHeader
Удаляет заголовки из запроса.
Важная деталь — фильтр работает с копией заголовков:
RewritePath / SetPath
Эти фильтры модифицируют путь запроса. RewritePath использует регулярные выражения для замены, а SetPath устанавливает фиксированный путь.
Внутренняя механика RewritePath:
#Java #middle #Spring_Cloud_Gateway #Filters
AddRequestHeader / AddResponseHeader
Эти фильтры добавляют заголовки к запросу или ответу.
Важно понимать их реактивную природу:
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// Модификация запроса (PRE-фильтр)
ServerHttpRequest mutatedRequest = request.mutate()
.header(headerName, headerValue)
.build();
// Создание нового обмена с модифицированным запросом
return chain.filter(exchange.mutate().request(mutatedRequest).build());
}Особенность: Заголовки добавляются в момент модификации запроса, который происходит синхронно при построении цепочки. Однако сама отправка модифицированного запроса к целевому сервису происходит асинхронно.
RemoveRequestHeader
Удаляет заголовки из запроса.
Важная деталь — фильтр работает с копией заголовков:
ServerHttpRequest mutated = exchange.getRequest().mutate()
.headers(headers -> {
headers.remove(headerName);
// или headers.removeIf(key -> key.startsWith("X-Secret-"));
})
.build();
RewritePath / SetPath
Эти фильтры модифицируют путь запроса. RewritePath использует регулярные выражения для замены, а SetPath устанавливает фиксированный путь.
Внутренняя механика RewritePath:
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getRawPath();
// Применение регулярного выражения
String newPath = path.replaceAll(regexp, replacement);
// Сохранение переменных из регулярного выражения
Map<String, String> variables = extractVariables(path, regexp);
exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, variables);
// Построение нового URI
URI newUri = UriComponentsBuilder.fromUri(exchange.getRequest().getURI())
.replacePath(newPath)
.build(true)
.toUri();
ServerHttpRequest mutated = exchange.getRequest().mutate()
.uri(newUri)
.build();
return chain.filter(exchange.mutate().request(mutated).build());
}#Java #middle #Spring_Cloud_Gateway #Filters
👍1
RequestRateLimiter
Один из самых сложных фильтров, реализующий ограничение частоты запросов. Работает с Redis для распределённого лимитирования.
Архитектура:
Алгоритм token bucket в Redis:
#Java #middle #Spring_Cloud_Gateway #Filters
Один из самых сложных фильтров, реализующий ограничение частоты запросов. Работает с Redis для распределённого лимитирования.
Архитектура:
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. Определение ключа для лимитирования
return keyResolver.resolve(exchange)
.switchIfEmpty(Mono.error(new IllegalStateException("Key not found")))
.flatMap(key -> {
// 2. Проверка лимита через Redis
return rateLimiter.isAllowed(key, config.getReplenishRate(),
config.getBurstCapacity(), config.getRequestedTokens())
.flatMap(response -> {
// 3. Добавление заголовков с информацией о лимите
exchange.getResponse().getHeaders()
.add("X-RateLimit-Remaining",
String.valueOf(response.getTokensLeft()));
exchange.getResponse().getHeaders()
.add("X-RateLimit-Replenish-Rate",
String.valueOf(config.getReplenishRate()));
if (response.isAllowed()) {
return chain.filter(exchange);
} else {
// 4. Отклонение запроса при превышении лимита
exchange.getResponse().setStatusCode(
HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
});
})
.onErrorResume(Exception.class, e -> {
// Обработка ошибок Redis
log.warn("Rate limiter error", e);
return chain.filter(exchange); // Разрешить запрос при ошибке
});
}Алгоритм token bucket в Redis:
-- Lua-скрипт, выполняемый атомарно в Redis
local tokens_key = KEYS[1] -- ключ для хранения токенов
local timestamp_key = KEYS[2] -- ключ для метки времени
local rate = tonumber(ARGV[1]) -- replenishRate
local capacity = tonumber(ARGV[2]) -- burstCapacity
local now = tonumber(ARGV[3]) -- текущее время
local requested = tonumber(ARGV[4]) -- requestedTokens
local fill_time = capacity / rate
local ttl = math.floor(fill_time * 2)
local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
last_tokens = capacity
end
local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
last_refreshed = 0
end
local delta = math.max(0, now - last_refreshed)
local filled_tokens = math.min(capacity, last_tokens + (delta * rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
if allowed then
new_tokens = filled_tokens - requested
end
redis.call("setex", tokens_key, ttl, new_tokens)
redis.call("setex", timestamp_key, ttl, now)
return { allowed, new_tokens }
#Java #middle #Spring_Cloud_Gateway #Filters
👍1
Retry / Circuit Breaker
Фильтры устойчивости, построенные на Resilience4J.
Retry фильтр:
Circuit Breaker фильтр:
ModifyResponseBody / ModifyRequestBody
Эти фильтры позволяют трансформировать тела запросов и ответов. Они требуют особого внимания из-за работы с потоком данных.
Архитектура ModifyRequestBody:
Критическое ограничение: Эти фильтры требуют чтения всего тела в память, что делает их непригодными для работы с большими файлами или стриминговыми данными.
#Java #middle #Spring_Cloud_Gateway #Filters
Фильтры устойчивости, построенные на Resilience4J.
Retry фильтр:
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return retryOperator.apply(chain.filter(exchange))
.onErrorResume(RetryExhaustedException.class, e -> {
// Все попытки исчерпаны
exchange.getResponse().setStatusCode(
HttpStatus.SERVICE_UNAVAILABLE);
return exchange.getResponse().setComplete();
});
}Circuit Breaker фильтр:
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return circuitBreaker.run(
chain.filter(exchange),
throwable -> {
// Fallback при открытом контуре или ошибке
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.SERVICE_UNAVAILABLE);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
byte[] fallbackBody = "{\"status\":\"unavailable\"}".getBytes();
DataBuffer buffer = response.bufferFactory()
.wrap(fallbackBody);
return response.writeWith(Mono.just(buffer));
}
);
}ModifyResponseBody / ModifyRequestBody
Эти фильтры позволяют трансформировать тела запросов и ответов. Они требуют особого внимания из-за работы с потоком данных.
Архитектура ModifyRequestBody:
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. Проверка Content-Type
MediaType contentType = exchange.getRequest().getHeaders().getContentType();
if (!config.getInClass().isInstance(contentType)) {
return chain.filter(exchange);
}
// 2. Чтение тела запроса в память (ограничение по size)
return DataBufferUtils.join(exchange.getRequest().getBody(), config.getMaxInMemorySize())
.switchIfEmpty(Mono.just(EMPTY_BUFFER))
.flatMap(dataBuffer -> {
// 3. Десериализация
return decode(dataBuffer, config.getInClass())
.flatMap(decodedBody -> {
// 4. Трансформация
return config.getRewriteFunction()
.apply(exchange, decodedBody)
.flatMap(encodedBody -> {
// 5. Сериализация обратно
byte[] bytes = encode(encodedBody, config.getOutClass());
// 6. Замена тела запроса
ServerHttpRequest mutated = exchange.getRequest().mutate()
.body(Flux.just(exchange.getResponse().bufferFactory()
.wrap(bytes)))
.build();
return chain.filter(
exchange.mutate().request(mutated).build()
);
});
})
.doFinally(signalType -> {
// 7. Освобождение буфера
DataBufferUtils.release(dataBuffer);
});
});
}Критическое ограничение: Эти фильтры требуют чтения всего тела в память, что делает их непригодными для работы с большими файлами или стриминговыми данными.
#Java #middle #Spring_Cloud_Gateway #Filters
👍1
Работа с телом запроса: проблемы и решения
Проблема повторного чтения body
Тело HTTP-запроса в реактивном стеке представлено как Flux<DataBuffer> — поток байтов, который может быть прочитан только один раз. При попытке повторного чтения возникает ошибка.
Неправильный подход:
Решения проблемы
1. Кэширование тела в памяти (для небольших запросов):
2. Использование ModifyRequestBody для трансформации:
3. Работа с потоком без буферизации (для больших данных):
#Java #middle #Spring_Cloud_Gateway #Filters
Проблема повторного чтения body
Тело HTTP-запроса в реактивном стеке представлено как Flux<DataBuffer> — поток байтов, который может быть прочитан только один раз. При попытке повторного чтения возникает ошибка.
Неправильный подход:
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// ПЕРВОЕ чтение
return exchange.getRequest().getBody()
.collectList()
.flatMap(buffers -> {
// ВТОРОЕ чтение (ОШИБКА!)
return exchange.getRequest().getBody()
.collectList()
.flatMap(...);
});
}Решения проблемы
1. Кэширование тела в памяти (для небольших запросов):
@Component
public class CacheRequestBodyFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Фильтр, который кэширует тело для последующего использования
return DataBufferUtils.join(exchange.getRequest().getBody())
.defaultIfEmpty(EMPTY_BUFFER)
.flatMap(dataBuffer -> {
// Сохраняем кэшированное тело в атрибутах
exchange.getAttributes().put(CACHED_REQUEST_BODY, dataBuffer);
// Создаём новый запрос с кэшированным телом
ServerHttpRequest mutated = exchange.getRequest().mutate()
.body(Flux.just(dataBuffer))
.build();
return chain.filter(exchange.mutate().request(mutated).build());
});
}
public static DataBuffer getCachedBody(ServerWebExchange exchange) {
return exchange.getAttribute(CACHED_REQUEST_BODY);
}
}
2. Использование ModifyRequestBody для трансформации:
filters:
- name: ModifyRequestBody
args:
inClass: String
outClass: String
rewriteFunction: "#{@myTransformer}"
3. Работа с потоком без буферизации (для больших данных):
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Преобразование потока на лету
Flux<DataBuffer> transformedBody = exchange.getRequest().getBody()
.map(dataBuffer -> {
// Трансформация каждого буфера
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
// Пример: преобразование в верхний регистр
String transformed = new String(bytes).toUpperCase();
return exchange.getResponse().bufferFactory()
.wrap(transformed.getBytes());
});
ServerHttpRequest mutated = exchange.getRequest().mutate()
.body(transformedBody)
.build();
return chain.filter(exchange.mutate().request(mutated).build());
}#Java #middle #Spring_Cloud_Gateway #Filters
👍1
Создание собственных фильтров
Реализация GatewayFilter через AbstractGatewayFilterFactory
Базовый фильтр с конфигурацией:
Использование в YAML:
Глобальные фильтры через GlobalFilter
Глобальные фильтры применяются ко всем маршрутам автоматически.
Пример: фильтр для добавления заголовка трассировки:
Фильтры с порядком выполнения
Порядок может быть задан через:
Аннотацию @Order
Реализацию интерфейса Ordered
Метод getOrder() в GatewayFilterFactory
Фильтр с динамическим порядком:
#Java #middle #Spring_Cloud_Gateway #Filters
Реализация GatewayFilter через AbstractGatewayFilterFactory
Базовый фильтр с конфигурацией:
@Component
public class LoggingGatewayFilterFactory extends
AbstractGatewayFilterFactory<LoggingGatewayFilterFactory.Config> {
public LoggingGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// PRE-обработка
if (config.isLogRequest()) {
logRequest(exchange, config);
}
long startTime = System.nanoTime();
// POST-обработка через then()
return chain.filter(exchange)
.then(Mono.fromRunnable(() -> {
if (config.isLogResponse()) {
long duration = System.nanoTime() - startTime;
logResponse(exchange, config, duration);
}
}));
};
}
public static class Config {
private boolean logRequest = true;
private boolean logResponse = true;
private LogLevel level = LogLevel.INFO;
// Геттеры и сеттеры
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("logRequest", "logResponse", "level");
}
}
Использование в YAML:
filters:
- Logging=true,true,DEBUG
Глобальные фильтры через GlobalFilter
Глобальные фильтры применяются ко всем маршрутам автоматически.
Пример: фильтр для добавления заголовка трассировки:
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class TraceIdGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Генерация или извлечение TraceId
String traceId = exchange.getRequest().getHeaders()
.getFirst("X-Trace-Id");
if (traceId == null || traceId.isEmpty()) {
traceId = UUID.randomUUID().toString();
}
// Добавление в заголовки запроса
ServerHttpRequest mutated = exchange.getRequest().mutate()
.header("X-Trace-Id", traceId)
.build();
// Добавление в атрибуты для использования в других фильтрах
exchange.getAttributes().put("traceId", traceId);
return chain.filter(exchange.mutate().request(mutated).build());
}
}
Фильтры с порядком выполнения
Порядок может быть задан через:
Аннотацию @Order
Реализацию интерфейса Ordered
Метод getOrder() в GatewayFilterFactory
Фильтр с динамическим порядком:
@Component
public class DynamicOrderFilter implements GlobalFilter, Ordered {
private final Environment environment;
private int order = 0;
public DynamicOrderFilter(Environment environment) {
this.environment = environment;
// Динамическое определение порядка из конфигурации
this.order = environment.getProperty("gateway.filter.order",
Integer.class, 0);
}
@Override
public int getOrder() {
return order;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Логика фильтра
return chain.filter(exchange);
}
}
#Java #middle #Spring_Cloud_Gateway #Filters
👍1
Сложный фильтр с асинхронными операциями
Пример: фильтр с внешним вызовом для валидации:
Фильтр с доступом к телу запроса
Безопасная работа с телом запроса:
#Java #middle #Spring_Cloud_Gateway #Filters
Пример: фильтр с внешним вызовом для валидации:
@Component
public class ExternalValidationFilter implements GlobalFilter {
private final WebClient webClient;
private final CircuitBreaker circuitBreaker;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. Извлечение данных для валидации
String apiKey = exchange.getRequest().getHeaders()
.getFirst("X-API-Key");
if (apiKey == null) {
return unauthorized(exchange);
}
// 2. Асинхронная валидация через внешний сервис
return circuitBreaker.run(
webClient.get()
.uri("/validate?apiKey={key}", apiKey)
.retrieve()
.bodyToMono(ValidationResponse.class)
.timeout(Duration.ofSeconds(5))
.onErrorResume(e -> Mono.just(new ValidationResponse(false))),
throwable -> Mono.just(new ValidationResponse(false))
)
.flatMap(validationResponse -> {
// 3. Обработка результата валидации
if (validationResponse.isValid()) {
// Добавление информации о пользователе в заголовки
ServerHttpRequest mutated = exchange.getRequest().mutate()
.header("X-User-Id", validationResponse.getUserId())
.header("X-User-Roles",
String.join(",", validationResponse.getRoles()))
.build();
return chain.filter(exchange.mutate().request(mutated).build());
} else {
return unauthorized(exchange);
}
});
}
private Mono<Void> unauthorized(ServerWebExchange exchange) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
Фильтр с доступом к телу запроса
Безопасная работа с телом запроса:
@Component
public class RequestBodyLoggingFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Только для определенных Content-Type
MediaType contentType = exchange.getRequest().getHeaders().getContentType();
if (contentType == null || !contentType.includes(MediaType.APPLICATION_JSON)) {
return chain.filter(exchange);
}
// Ограничение размера для логирования
final int maxBodySize = 1024 * 10; // 10KB
return DataBufferUtils.join(exchange.getRequest().getBody(), maxBodySize)
.defaultIfEmpty(EMPTY_BUFFER)
.flatMap(dataBuffer -> {
try {
// Чтение и логирование
byte[] bytes = new byte[Math.min(dataBuffer.readableByteCount(), maxBodySize)];
dataBuffer.read(bytes);
String body = new String(bytes, StandardCharsets.UTF_8);
log.debug("Request body (first {} bytes): {}", bytes.length, body);
// Восстановление тела
DataBuffer restoredBuffer = exchange.getResponse().bufferFactory()
.wrap(bytes);
// Продолжение цепочки с восстановленным телом
ServerHttpRequest mutated = exchange.getRequest().mutate()
.body(Flux.just(restoredBuffer)
.concatWith(exchange.getRequest().getBody()
.skipUntil(buffer -> false))) // Пропускаем уже прочитанное
.build();
return chain.filter(exchange.mutate().request(mutated).build());
} finally {
DataBufferUtils.release(dataBuffer);
}
});
}
}
#Java #middle #Spring_Cloud_Gateway #Filters
👍1
Оптимизация и best practices
Избегание блокирующих операций
Неправильно (блокирующий вызов):
Правильно (асинхронная обёртка):
Управление памятью и ресурсами
Освобождение DataBuffer:
Мониторинг и метрики фильтров
#Java #middle #Spring_Cloud_Gateway #Filters
Избегание блокирующих операций
Неправильно (блокирующий вызов):
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Блокирующий вызов в реактивном контексте
String result = blockingService.call(); // ОПАСНО!
return chain.filter(exchange);
}Правильно (асинхронная обёртка):
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Обёртка блокирующего вызова в отдельном пуле потоков
return Mono.fromCallable(() -> blockingService.call())
.subscribeOn(Schedulers.boundedElastic()) // Выделенный пул для блокирующих операций
.flatMap(result -> {
// Использование результата
return chain.filter(exchange);
});
}Управление памятью и ресурсами
Освобождение DataBuffer:
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
try {
// Работа с буфером
return processBuffer(dataBuffer)
.flatMap(result -> chain.filter(exchange));
} finally {
// Обязательное освобождение
DataBufferUtils.release(dataBuffer);
}
});
}Мониторинг и метрики фильтров
@Component
public class MetricsGlobalFilter implements GlobalFilter {
private final MeterRegistry meterRegistry;
private final Map<String, Timer> timerCache = new ConcurrentHashMap<>();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long startTime = System.nanoTime();
String routeId = exchange.getAttribute(ROUTE_ATTRIBUTE_ID);
return chain.filter(exchange)
.doOnSuccess(v -> recordSuccess(routeId, startTime))
.doOnError(e -> recordError(routeId, startTime, e));
}
private void recordSuccess(String routeId, long startTime) {
long duration = System.nanoTime() - startTime;
Timer timer = timerCache.computeIfAbsent(routeId, id ->
Timer.builder("gateway.request.duration")
.tag("route", id)
.tag("status", "success")
.register(meterRegistry));
timer.record(duration, TimeUnit.NANOSECONDS);
}
}
#Java #middle #Spring_Cloud_Gateway #Filters
👍1