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

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

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Фильтры устойчивости и ограничения:
filters:
# Circuit Breaker с Resilience4J
- name: CircuitBreaker
args:
name: userServiceBreaker
fallbackUri: forward:/fallback/user
statusCodes: 500,502,503

# Ограничение частоты запросов с Redis
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
redis-rate-limiter.requestedTokens: 1
key-resolver: "#{@userKeyResolver}"

# Retry с экспоненциальной отсрочкой
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,INTERNAL_SERVER_ERROR
methods: GET,POST
series: SERVER_ERROR
backoff:
firstBackoff: 50ms
maxBackoff: 1000ms
factor: 2
basedOnPreviousValue: false



URI и метаданные

URI назначения поддерживает несколько схем:
uri: http://localhost:8081           # Прямой URL
uri: https://api.example.com:8443 # HTTPS
uri: lb://USER-SERVICE # Балансировка нагрузки через Service Discovery
uri: ws://echo.example.com # WebSocket
uri: forward:/fallback # Внутреннее перенаправление

Схема lb:// активирует клиентскую балансировку нагрузки. При использовании с Service Discovery (Eureka, Consul) имя сервиса автоматически разрешается в список доступных экземпляров.


Метаданные маршрута предоставляют дополнительную конфигурацию, специфичную для маршрута:
metadata:
# Таймауты для Netty
response-timeout: 5000 # Таймаут ответа в миллисекундах
connect-timeout: 2000 # Таймаут соединения
max-auto-retries: 2 # Автоматические повторы для сетевых ошибок

# Конфигурация для конкретных интеграций
hystrix.commandName: userCommand
metrics.tags: "service=user,version=v2"

# Пользовательские метаданные
feature-flags: "canary=true,experimental=false"
sla: "p99<200ms"

Метаданные доступны в фильтрах через exchange.getAttribute(ROUTE_ATTRIBUTE).getMetadata() и могут использоваться для реализации динамического поведения.



#Java #middle #Spring_Cloud_Gateway
👍2
Java DSL: Fluent API для программируемой конфигурации

Java DSL через RouteLocatorBuilder предоставляет императивный способ определения маршрутов, который позволяет использовать всю мощь Java: условия, циклы, вызовы методов, dependency injection.

Базовый fluent-синтаксис

@Configuration
public class GatewayConfiguration {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user_route", r -> r
.path("/api/users/**")
.and()
.method(HttpMethod.GET, HttpMethod.POST)
.and()
.header("X-API-Version", "v2")
.filters(f -> f
.stripPrefix(1)
.addRequestHeader("X-Gateway-Route", "user")
.circuitBreaker(config -> config
.setName("userCircuitBreaker")
.setFallbackUri("forward:/fallback/user"))
.retry(config -> config
.setRetries(3)
.setStatuses(HttpStatus.INTERNAL_SERVER_ERROR,
HttpStatus.BAD_GATEWAY)
.setMethods(HttpMethod.GET)
.setBackoff(50, 200, 2, true))
)
.uri("lb://user-service")
.metadata("response-timeout", 3000)
.metadata("connect-timeout", 1000)
)
.route("product_route", r -> r
.path("/api/products/**")
.filters(f -> f
.rewritePath("/api/products/(?<segment>.*)",
"/v2/products/${segment}")
.addResponseHeader("X-Cache-Control", "max-age=3600")
.requestRateLimiter(config -> config
.setRateLimiter(redisRateLimiter())
.setKeyResolver(userKeyResolver()))
)
.uri("lb://product-service")
)
.build();
}

@Bean
public RedisRateLimiter redisRateLimiter() {
return new RedisRateLimiter(10, 20, 1);
}

@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getHeaders()
.getFirst("X-User-Id")
);
}
}



Преимущества Java DSL перед YAML

1. Использование Spring Beans в конфигурации:
@Bean
public RouteLocator dynamicRouteLocator(
RouteLocatorBuilder builder,
FeatureFlagService featureService,
CircuitBreakerRegistry breakerRegistry) {

return builder.routes()
.route("feature_route", r -> r
.path("/experimental/**")
.predicate(exchange -> {
// Динамическая проверка feature flags
boolean enabled = featureService.isEnabled(
"experimental_api",
exchange.getRequest().getHeaders()
.getFirst("X-User-Id")
);
return enabled;
})
.filters(f -> f
.circuitBreaker(config -> config
.setCircuitBreakerFactory(
new ReactiveResilience4JCircuitBreakerFactory(
breakerRegistry
)
)
.setName("experimentalBreaker")
)
)
.uri("lb://experimental-service")
)
.build();
}



#Java #middle #Spring_Cloud_Gateway
👍3
2. Генерация маршрутов на основе данных из внешних источников:
@Bean
public RouteLocator generatedRoutes(
RouteLocatorBuilder builder,
RouteTemplateService templateService) {

RouteLocatorBuilder.Builder routesBuilder = builder.routes();

// Загрузка шаблонов маршрутов из базы данных
List<RouteTemplate> templates = templateService.loadTemplates();

for (RouteTemplate template : templates) {
routesBuilder.route(template.getId(), r -> {
RouteLocatorBuilder.Builder spec = r
.path(template.getPathPattern())
.uri(template.getTargetUri());

// Динамическое добавление фильтров
for (FilterConfig filterConfig : template.getFilters()) {
spec.filters(f -> addFilterDynamically(f, filterConfig));
}

return spec;
});
}

return routesBuilder.build();
}


3. Создание сложных составных предикатов:
@Bean
public RouteLocator complexPredicateRoute(RouteLocatorBuilder builder) {
return builder.routes()
.route("business_rule_route", r -> r
// Комбинация предикатов с кастомной логикой
.asyncPredicate(exchange -> {
return businessRuleEngine()
.evaluate(exchange.getRequest())
.map(BusinessDecision::isAllowed);
})
.and()
.weight("group_a", 80) // 80% трафика
.and()
.cookie("session_id", ".*")
.filters(f -> f
.modifyRequestBody(String.class, String.class,
(exchange, body) -> {
// Модификация тела на основе бизнес-правил
return Mono.just(
transformPayload(body, exchange)
);
}
)
)
.uri("lb://primary-service")
)
.build();
}



Что можно делать только в DSL и нельзя в YAML

1. Использование произвольных Predicate<ServerWebExchange>:
.route("custom_predicate", r -> r
.predicate(exchange -> {
// Любая Java-логика
String clientIp = exchange.getRequest()
.getRemoteAddress()
.getAddress()
.getHostAddress();

return !ipBlacklist.contains(clientIp) &&
rateLimiter.tryAcquire(clientIp);
})
.uri("lb://service")
)


2. Интеграция с внешними системами конфигурации:
.route("external_config", r -> r
.asyncPredicate(exchange -> {
// Загрузка правил из внешнего сервиса
return configClient.getRoutingRules()
.map(rules -> rules.matches(exchange.getRequest()));
})
.filters((exchange, chain) -> {
// Динамическая модификация на основе конфигурации
ServerHttpRequest request = exchange.getRequest();
Map<String, String> headers = externalConfig
.getHeadersForRoute(request.getPath().toString());

ServerHttpRequest mutated = request.mutate()
.headers(h -> headers.forEach(h::add))
.build();

return chain.filter(
exchange.mutate().request(mutated).build()
);
})
.uri("lb://target")
)


#Java #middle #Spring_Cloud_Gateway
👍3
3. Генерация маршрутов в runtime:
@Bean
public RouteLocator runtimeGeneratedRoutes(
RouteLocatorBuilder builder,
ApplicationContext context) {

RouteLocatorBuilder.Builder routes = builder.routes();

// Динамическое создание маршрутов на основе Bean-ов
Map<String, RouteProvider> providers = context
.getBeansOfType(RouteProvider.class);

providers.forEach((name, provider) -> {
provider.getRoutes().forEach(routeDef -> {
routes.route(routeDef.getId(), r -> {
AbstractRouteSpec spec = r
.path(routeDef.getPath())
.uri(routeDef.getUri());

routeDef.getFilters().forEach(filter -> {
spec.filters(f -> configureFilter(f, filter));
});

return spec;
});
});
});

return routes.build();
}



Динамическая маршрутизация

DiscoveryClientRouteDefinitionLocator

DiscoveryClientRouteDefinitionLocator обеспечивает автоматическое создание маршрутов на основе сервисов, зарегистрированных в Service Discovery. При активации, для каждого зарегистрированного сервиса создаётся маршрут, который перенаправляет трафик на этот сервис.

spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
predicates:
- name: Path
args:
pattern: "'/services/' + serviceId.toLowerCase() + '/**'"
filters:
- name: RewritePath
args:
regexp: "'/services/' + serviceId.toLowerCase() + '/(?<remaining>.*)'"
replacement: "'/${remaining}'"


Эта конфигурация создаст маршруты вида:
http://gateway/services/user-service/** → lb://user-service
http://
gateway/services/order-service/** → lb://order-service


Интеграция с Eureka

Для интеграции с Eureka необходимо добавить зависимость и соответствующую конфигурацию:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>


eureka:
client:
service-url:
defaultZone: http://eureka-server:8761/eureka
registry-fetch-interval-seconds: 5
instance:
lease-renewal-interval-in-seconds: 10
lease-expiration-duration-in-seconds: 30

spring:
cloud:
gateway:
discovery:
locator:
enabled: true
# Добавление метаданных из Eureka в маршруты
include-expression: metadata['gateway.enabled'] == 'true'
url-expression: "'lb://' + serviceId"


Eureka-специфичные метаданные могут использоваться для управления маршрутизацией:
@Bean
public RouteLocator eurekaAwareRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("zone_aware", r -> r
.path("/zone/**")
.predicate(exchange -> {
// Выбор экземпляра в той же зоне доступности
ServiceInstance instance = loadBalancer.choose(
"target-service",
new ZonePreferenceServiceInstanceListSupplier()
);
return instance != null;
})
.uri("lb://target-service")
)
.build();
}



#Java #middle #Spring_Cloud_Gateway
👍2
Интеграция с Consul

Consul предоставляет более богатые возможности для service discovery и health checking:

spring:
cloud:
consul:
host: localhost
port: 8500
discovery:
health-check-path: /actuator/health
health-check-interval: 10s
tags:
- gateway-enabled
- version=v2
query-passing: true # Использовать только здоровые инстансы

gateway:
discovery:
locator:
enabled: true
predicates:
- name: Path
args:
pattern: "'/consul/' + serviceId + '/**'"
filters:
- name: RewritePath
args:
regexp: "'/consul/' + serviceId + '/(?<remaining>.*)'"
replacement: "'/${remaining}'"
# Фильтрация по Consul-тегам
include-expression: tags.contains('gateway-enabled')


Consul теги могут использоваться для реализации сложной логики маршрутизации:
@Bean
public RouteLocator consulTagBasedRoutes(
RouteLocatorBuilder builder,
ConsulDiscoveryClient discoveryClient) {

return builder.routes()
.route("canary_route", r -> r
.path("/api/canary/**")
.predicate(exchange -> {
// Динамическая канареечная маршрутизация
List<ServiceInstance> instances = discoveryClient
.getInstances("product-service");

// Выбор инстансов с тегом canary
ServiceInstance canaryInstance = instances.stream()
.filter(inst -> inst.getMetadata()
.getOrDefault("canary", "false")
.equals("true"))
.findFirst()
.orElse(null);

// 10% трафика на canary
return canaryInstance != null &&
Math.random() < 0.1;
})
.uri("lb://product-service")
)
.build();
}



#Java #middle #Spring_Cloud_Gateway
👍2
Кастомный RouteDefinitionRepository

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

Базовая реализация с поддержкой обновлений:

@Component
public class DatabaseRouteDefinitionRepository
implements RouteDefinitionRepository, ApplicationEventPublisherAware {

private final RouteDefinitionDAO routeDefinitionDAO;
private final Map<String, RouteDefinition> cache =
new ConcurrentHashMap<>();
private ApplicationEventPublisher publisher;

public DatabaseRouteDefinitionRepository(
RouteDefinitionDAO routeDefinitionDAO) {
this.routeDefinitionDAO = routeDefinitionDAO;
loadRoutes();
}

@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(cache.values());
}

@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap(routeDef -> {
return routeDefinitionDAO.save(routeDef)
.doOnSuccess(saved -> {
cache.put(saved.getId(), saved);
publishRefreshEvent();
})
.then();
});
}

@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id -> {
return routeDefinitionDAO.delete(id)
.doOnSuccess(deleted -> {
cache.remove(id);
publishRefreshEvent();
})
.then();
});
}

private void loadRoutes() {
routeDefinitionDAO.findAll()
.doOnNext(routeDef -> cache.put(routeDef.getId(), routeDef))
.subscribe();
}

private void publishRefreshEvent() {
if (publisher != null) {
publisher.publishEvent(
new RefreshRoutesEvent(this)
);
}
}

@Override
public void setApplicationEventPublisher(
ApplicationEventPublisher publisher) {
this.publisher = publisher;
}

// Метод для внешних триггеров обновления
@Scheduled(fixedDelay = 30000)
public void refreshRoutes() {
loadRoutes();
publishRefreshEvent();
}
}


Реализация с поддержкой веб-интерфейса для управления маршрутами:
@RestController
@RequestMapping("/api/gateway/routes")
public class RouteManagementController {

private final RouteDefinitionWriter routeDefinitionWriter;
private final RouteDefinitionLocator routeDefinitionLocator;

@PostMapping
public Mono<ResponseEntity<Void>> createRoute(
@RequestBody RouteDefinition routeDefinition) {
return routeDefinitionWriter
.save(Mono.just(routeDefinition))
.then(Mono.just(ResponseEntity.ok().build()));
}

@GetMapping
public Flux<RouteDefinition> getAllRoutes() {
return routeDefinitionLocator.getRouteDefinitions();
}

@PutMapping("/{id}")
public Mono<ResponseEntity<Void>> updateRoute(
@PathVariable String id,
@RequestBody RouteDefinition routeDefinition) {

return routeDefinitionWriter
.delete(Mono.just(id))
.then(routeDefinitionWriter.save(Mono.just(routeDefinition)))
.then(Mono.just(ResponseEntity.ok().build()));
}

@DeleteMapping("/{id}")
public Mono<ResponseEntity<Void>> deleteRoute(@PathVariable String id) {
return routeDefinitionWriter
.delete(Mono.just(id))
.then(Mono.just(ResponseEntity.noContent().build()));
}
}


#Java #middle #Spring_Cloud_Gateway
👍2
Интеграция с внешними системами конфигурации:
@Component
public class ExternalConfigRouteDefinitionRepository
implements RouteDefinitionRepository {

private final ConfigClient configClient;
private final AtomicReference<List<RouteDefinition>> cachedRoutes =
new AtomicReference<>(Collections.emptyList());

public ExternalConfigRouteDefinitionRepository(
ConfigClient configClient) {
this.configClient = configClient;
configClient.watchRoutes()
.doOnNext(this::updateRoutes)
.subscribe();
}

@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(cachedRoutes.get());
}

private void updateRoutes(List<RouteConfig> routeConfigs) {
List<RouteDefinition> routeDefinitions = routeConfigs.stream()
.map(this::convertToRouteDefinition)
.collect(Collectors.toList());

cachedRoutes.set(routeDefinitions);

// Генерация события обновления маршрутов
SpringApplication.publishEvent(
new RefreshRoutesEvent(this)
);
}

private RouteDefinition convertToRouteDefinition(
RouteConfig config) {
RouteDefinition definition = new RouteDefinition();
definition.setId(config.getId());
definition.setUri(URI.create(config.getUri()));

// Конвертация предикатов
config.getPredicates().forEach((name, args) -> {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(name);
predicate.setArgs(args);
definition.getPredicates().add(predicate);
});

// Конвертация фильтров
config.getFilters().forEach((name, args) -> {
FilterDefinition filter = new FilterDefinition();
filter.setName(name);
filter.setArgs(args);
definition.getFilters().add(filter);
});

definition.setMetadata(config.getMetadata());
return definition;
}

@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
// Делегирование сохранения во внешнюю систему
return route.flatMap(routeDef ->
configClient.saveRoute(convertToRouteConfig(routeDef))
).then();
}

@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id ->
configClient.deleteRoute(id)
).then();
}
}


Обработка событий обновления маршрутов:
@Component
public class RouteUpdateListener {

private final RouteRefreshListener routeRefreshListener;

@EventListener
public void handleRefreshRoutesEvent(RefreshRoutesEvent event) {
// Логирование обновления маршрутов
logger.info("Routes refreshed by {}", event.getSource());

// Метрики
meterRegistry.counter("gateway.routes.refresh").increment();

// Уведомление подписчиков
routeRefreshListener.notifyRefresh();
}

@EventListener
public void handleApplicationEvent(ApplicationEvent event) {
if (event instanceof InstanceRegisteredEvent) {
// Обработка регистрации нового инстанса
updateRoutesForNewInstance(
((InstanceRegisteredEvent) event).getInstance()
);
}
}
}


#Java #middle #Spring_Cloud_Gateway
👍2