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

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

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Детальный разбор встроенных фильтров

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
👍2
RequestRateLimiter

Один из самых сложных фильтров, реализующий ограничение частоты запросов. Работает с 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
👍2
Retry / Circuit Breaker

Фильтры устойчивости, построенные на 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
👍2
Работа с телом запроса: проблемы и решения

Проблема повторного чтения 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
👍2
Создание собственных фильтров

Реализация 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
👍2
Сложный фильтр с асинхронными операциями

Пример: фильтр с внешним вызовом для валидации:
@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
👍2
Оптимизация и best practices

Избегание блокирующих операций

Неправильно (блокирующий вызов):
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
👍2
Что выведет код?

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class Task171225 {
public static void main(String[] args) {
Map<String, Integer> failFast = new HashMap<>();
Map<String, Integer> failSafe = new ConcurrentHashMap<>();

failFast.put("A", 1);
failFast.put("B", 2);
failSafe.put("A", 1);
failSafe.put("B", 2);

try {
for (String key : failFast.keySet()) {
System.out.print(key + " ");
if (key.equals("A")) failFast.remove("B");
}
} catch (Exception e) {
System.out.print("FFex ");
}

try {
for (String key : failSafe.keySet()) {
System.out.print(key + " ");
if (key.equals("A")) failSafe.remove("B");
}
} catch (Exception e) {
System.out.print("FSex ");
}

System.out.print(failFast.size() + " ");
System.out.print(failSafe.size());
}
}


#Tasks
👍1
Вопрос с собеседований

Что делает ByteBuffer? 🤓

Ответ:

ByteBuffer предоставляет низкоуровневый способ работы с байтами.

Позволяет управлять позицией, лимитом, ёмкостью, использовать direct-память вне heap.

Такой подход повышает производительность сетевых серверов и NIO-каналов.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
История IT-технологий сегодня — 18 декабря


ℹ️ Кто родился в этот день

Джо́зеф Джон То́мсон (англ. Joseph John Thomson; 18 декабря 1856, Читем-Хилл, Манчестер — 30 августа 1940, Кембридж)английский физик, лауреат Нобелевской премии по физике 1906 года с формулировкой «за исследования прохождения электричества через газы». Исследование «катодных лучей» (электронных пучков), в результате которого было показано, что они имеют корпускулярную природу и состоят из отрицательно заряженных частиц субатомного размера. Эти исследования привели к открытию электрона (1897).


🌐 Знаковые события

1953 — начато цветное телевизионное вещание с использованием системы NTSC.

1958 — запущен первый в мире спутник связи.


1987 — выпущена первая версия языка Perl.


#Biography #Birth_Date #Events #18Декабря
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Раздел 6. Коллекции в Java

Глава 6. Итераторы

Практика: В «Библиотеке» реализовать метод обхода всех книг с помощью Iterator и for-each. Сделать возможность удаления книги во время обхода

Подготовка

Убедитесь, что ваш проект готов, и вспомните ключевые концепции итераторов:
for-each — синтаксический сахар над Iterator.
Iterator позволяет безопасно удалять элементы во время перебора (метод remove()).
Прямое удаление через list.remove() во время for-each — ConcurrentModificationException.


Откройте проект «Библиотека»: Запустите IntelliJ IDEA и откройте проект LibraryProject. Убедитесь, что класс Library содержит List<Book> books (ArrayList) и методы addBook, findBookByTitle и т.д.
Импортируйте необходимые пакеты: В файле
Library.java импортируйте java.util.Iterator (IDE подскажет при написании).


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

Мы добавим два метода в класс Library: один с явным Iterator, второй с for-each, и покажем, как безопасно удалять во время итерации.

Создайте метод printAllBooksWithIterator():
Метод должен быть публичным и void.
Получите Iterator<Book> из списка books с помощью books.iterator().
В цикле while используйте hasNext() для проверки наличия следующего элемента.
Внутри цикла получите текущую книгу через next().
Вызовите printDetails() на книге или выведите её информацию.
(Опционально) Добавьте нумерацию (int index = 0; index++).


Создайте метод printAllBooksWithForEach():
Метод аналогичен предыдущему, но используйте for-each цикл: for (Book book : books).
Внутри цикла вызовите printDetails() на книге.


Создайте метод removeBookDuringIteration(String titleToRemove):
Метод должен безопасно удалять книгу по названию во время итерации.
Получите Iterator<Book>.

В цикле while (iterator.hasNext()):
Получите текущую книгу через next().
Сравните её title с titleToRemove (equalsIgnoreCase для удобства).
Если совпадение — вызовите iterator.remove() (не list.remove!).
Выведите сообщение "Книга удалена: [title]".
После цикла выведите "Обход завершён".
Важно: Используйте именно iterator.remove() — это единственный безопасный способ удаления во время итерации. Прямой books.remove(book) вызовет ConcurrentModificationException.


Обновление класса Main для тестирования

Теперь протестируем новые методы в Main.

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


Протестируйте обход:
Вызовите printAllBooksWithIterator() — увидите все книги.
Вызовите printAllBooksWithForEach() — тот же результат.


Протестируйте удаление во время итерации:
Вызовите removeBookDuringIteration() с названием существующей книги.
После удаления вызовите printAllBooks...() — книга должна исчезнуть.
Попробуйте удалить несуществующую книгу — ничего не удалится, но обход завершится.


Демонстрация ошибки (опционально):
Создайте отдельный метод с for-each и внутри цикла books.remove(book) — запустите и поймайте ConcurrentModificationException (в реальном коде не делайте так!).


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

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

Проверьте поведение:
Удаление работает только через iterator.remove().
После remove список уменьшается, последующие книги выводятся корректно.


Отладка:
Установите breakpoint в цикле iterator.hasNext() и next().
Шагайте (F8) — увидите, как iterator перемещается и как remove() влияет на список.
В watch добавьте books.size() — увидите уменьшение.


Эксперименты:
Попробуйте модифицировать книгу во время итерации (book.setTitle(...)) — это разрешено.
Добавьте несколько удалений в одном проходе — iterator.remove() безопасен.



#Java #для_новичков #beginner #for_each #fail_fast #fail_safe #Практика
Полезные советы для новичков

Iterator vs for-each: for-each проще, но не позволяет удалять. Iterator — для модификации.
remove() итератора: Вызывается после next(), удаляет последний полученный элемент.
ConcurrentModificationException: Избегайте прямого list.remove() в for-each.
Альтернатива: Для удаления нескольких элементов соберите индексы/объекты в другой список, затем удалите после итерации.
Импорты: import java.util.Iterator;



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

Задача 1:
Добавьте в Library метод removeAllByAuthor(String author), который использует Iterator для удаления всех книг данного автора.
Задача 2: Реализуйте метод printBooksBackwards() — используйте ListIterator (books.listIterator(books.size())) и previous() для обратного обхода.
Задача 3: Протестируйте удаление во время итерации с несколькими совпадениями — убедитесь, что все удаляются безопасно.


Реализуйте эти задачи самостоятельно — это закрепит работу с Iterator и понимание итерации.


#Java #для_новичков #beginner #for_each #fail_fast #fail_safe #Практика
🔥1🆒1
Что выведет код?

import java.util.*;

public class Task181225 {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

try {
list.forEach(item -> {
System.out.print(item + " ");
if (item.equals("b")) {
list.add("d");
}
});
} catch (Exception e) {
System.out.print("Exception ");
}

System.out.println(list.size());
}
}


#Java
🔥2
Варианты ответа:
Anonymous Quiz
22%
a b c Exception 3
0%
a b c 4
67%
a b Exception 4
11%
a b c d 4
🔥1
Вопрос с собеседований

Как работает Selector в NIO? 🤓

Ответ:

Selector
позволяет одному потоку обслуживать множество каналов с неблокирующим I/O.

Поток получает события готовности: чтение, запись, подключение.

Это основа серверов с тысячами соединений без создания тысяч потоков.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
История IT-технологий сегодня — 19 декабря


ℹ️ Кто родился в этот день

Орна Берри ( ивр . ארנה ברי ; родилась 19 декабря 1949 г.) — израильский учёный-информатик, бизнес-лидер и предприниматель в области ИТ; участвовала в развитии технологий и стартапов, работала на руководящих инженерных позициях в IBM и других технологических компаниях.


🌐 Знаковые события

2013 — Европейское космическое агентство вывело на орбиту космический телескоп Gaia.


#Biography #Birth_Date #Events #19Декабря
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Интеграция Spring Cloud Gateway с Spring Security: Архитектура реактивной безопасности

Двойная цепочка фильтров: концептуальная модель

При интеграции Spring Security с Spring Cloud Gateway формируется двухуровневая архитектура обработки запросов, где каждая цепочка фильтров выполняет свою специализированную роль. Spring Security оперирует на уровне SecurityWebFilterChain, отвечая за аутентификацию и авторизацию, в то время как Spring Cloud Gateway управляет GatewayFilterChain, специализируясь на маршрутизации и трансформации трафика.

Архитектурно это представляет собой последовательный pipeline, где запрос сначала проходит через механизмы безопасности, и только после успешной проверки попадает в систему маршрутизации. Такой подход обеспечивает принцип "security first" — ни один запрос не достигает бизнес-логики без предварительной проверки прав доступа.

Реактивная реализация обеих цепочек означает, что они построены на неблокирующей модели, где каждый фильтр описывает отложенные вычисления в виде реактивных потоков (Mono/Flux), а не выполняет синхронные операции. Это позволяет системе эффективно обрабатывать тысячи одновременных соединений с минимальным количеством потоков.



Реактивный контекст безопасности

В отличие от традиционного Spring Security, который хранит контекст аутентификации в ThreadLocal, реактивная версия использует ReactiveSecurityContextHolder, основанный на реактивном контексте (Context). Этот контекст распространяется через реактивный pipeline с помощью операторов subscriberContext() или contextWrite().

Когда запрос проходит через цепочку безопасности, аутентификационный объект помещается в реактивный контекст, который затем автоматически распространяется на все последующие стадии обработки, включая фильтры Gateway и вызовы downstream сервисов. Это обеспечивает сквозную передачу информации об аутентификации без необходимости явной передачи токенов или других идентификаторов между компонентами.



#Java #middle #Spring_Cloud_Gateway
👍2
Принципы Token Relay в распределённых системах

Концептуальные модели передачи токенов

Token Relay (передача токенов) — это архитектурный паттерн, при котором API Gateway передаёт токены аутентификации от клиента к внутренним сервисам. Существует три основные стратегии реализации этого паттерна, каждая со своей областью применения и компромиссами.

Pass-through модель — наиболее простая стратегия, при которой Gateway передаёт исходный токен клиента (обычно JWT) без изменений. Этот подход сохраняет оригинальную сигнатуру токена и все claims, что упрощает аудит, но создаёт потенциальную уязвимость: внутренние сервисы получают полный доступ к claims, которые могут содержать конфиденциальную информацию, не предназначенную для их уровня доступа.

Token Exchange модель предполагает замену клиентского токена на новый, с ограниченным набором claims и scope, специфичным для целевого сервиса. Gateway выступает в роли trusted party, которая аутентифицирует клиента, а затем запрашивает у сервера авторизации новый токен с уменьшенными привилегиями. Этот подход реализует принцип минимальных привилегий, но увеличивает задержку и создаёт зависимость от сервиса авторизации.

Token Enrichment модель — гибридный подход, при котором Gateway добавляет дополнительные claims к исходному токену, не изменяя его базовую структуру. Например, Gateway может добавить информацию о маршруте, уровне обслуживания или других контекстных данных. Этот подход балансирует между безопасностью и производительностью, но требует тщательной проверки целостности токена.


Безопасность передачи токенов

При реализации Token Relay критически важны механизмы защиты от атак. Gateway должен проверять целостность токенов (подпись JWT), срок действия, издателя (issuer) и аудиторию (audience). Для предотвращения replay-атак может использоваться механизм одноразовых номеров (nonce) или проверка времени выпуска токена (iat claim).

Распространённой практикой является использование двойной проверки токенов: Gateway проверяет токен при поступлении запроса, а внутренние сервисы могут выполнять дополнительную, более детальную проверку, специфичную для их домена. Это создаёт defense-in-depth архитектуру, где компрометация одного компонента не приводит к полному нарушению безопасности системы.



#Java #middle #Spring_Cloud_Gateway
👍2
Интеграция с внешними системами аутентификации

Архитектурные паттерны интеграции

Интеграция Gateway с внешними провайдерами аутентификации (Keycloak, Auth0, Okta) следует одному из двух архитектурных паттернов: делегированной аутентификации или федерации идентификации.


При делегированной аутентификации Gateway перенаправляет клиента к внешнему провайдеру для аутентификации, а затем получает токен, который использует для доступа к внутренним сервисам. Gateway в этом случае выступает как OAuth2 client или OpenID Connect Relying Party. Этот паттерн обеспечивает максимальную безопасность, так как учётные данные никогда не проходят через Gateway, но создаёт дополнительное взаимодействие с внешним сервисом.


Федерация идентификации предполагает, что Gateway самостоятельно проверяет токены, выпущенные внешним провайдером, используя публичные ключи или эндпоинты обнаружения. Gateway загружает конфигурацию провайдера (например, из .well-known/openid-configuration) и кэширует публичные ключи для проверки подписи. Этот паттерн уменьшает зависимость от внешнего сервиса во время обработки каждого запроса, но требует тщательной настройки механизмов обновления ключей и конфигурации.


Обработка ошибок безопасности в реактивном контексте

Теория обработки исключений в реактивных цепочках

В реактивном программировании исключения обрабатываются иначе, чем в императивном коде. Вместо блоков try-catch используются операторы onErrorResume, onErrorReturn и doOnError, которые позволяют обрабатывать ошибки в потоке данных без прерывания выполнения цепочки.

Когда фильтр безопасности обнаруживает нарушение (например, невалидный токен или недостаточные права), он не бросает исключение в традиционном смысле, а возвращает Mono.error(), который создаёт сигнал ошибки в реактивном потоке. Этот сигнал затем перехватывается обработчиками ошибок, которые преобразуют его в соответствующий HTTP-ответ.

Архитектурно важно различать ошибки аутентификации (401) и авторизации (403). Ошибки аутентификации указывают на проблему с установлением личности пользователя, в то время как ошибки авторизации возникают, когда аутентифицированный пользователь пытается выполнить действие, на которое у него нет прав. Gateway должен различать эти случаи, даже когда внешний провайдер возвращает унифицированные коды ошибок.


Каскадная обработка ошибок безопасности

В многоуровневой системе безопасности ошибки могут возникать на разных этапах: при разборе заголовков, проверке токена, валидации claims, проверке прав доступа. Архитектура должна обеспечивать согласованную обработку всех типов ошибок с сохранением контекста, необходимого для отладки и аудита.

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

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



#Java #middle #Spring_Cloud_Gateway
👍2