31 Temmuz 2021 Cumartesi

SpringWebFlux RxJava2Adapter Sınıfı

Project Reactor sınıflarından RxJava'ya dönüşüm içindir

monoToSingle metodu
Örnek
Elimizde Mono döndüren bir metod olsun. Şöyle yaparız
@GetMapping("/rxjavanonblocking")
public Single<String> getURLRxJavaNonBlocking() {
  log.info("querying google");
  return RxJava2Adapter.monoToSingle(
    getHttpNonBlocking("https://www.google.com")
    .doOnNext(s -> log.info("found content length {}", s.length())));
}

SpringWebFlux WebClient Mono Dönüşümü

Giriş
WebClient cevabını Mono'ya dönüştürmek için yöntemler şöyle

1. bodyToMono
Örnek
Şöyle yaparız
WebClient webClient = WebClient.create();
Mono<String> responseOne = webClient.get()
    .uri("http://example.com/endpointOne")
    .retrieve()
    .bodyToMono(String.class);

Mono<String> responseTwo = webClient.get()
    .uri("http://example.com/endpointTwo")
    .retrieve()
    .bodyToMono(String.class);

// Use Mono.zip to execute requests concurrently
Mono.zip(responseOne, responseTwo).subscribe(results -> {
    System.out.println("Result 1: " + results.getT1());
    System.out.println("Result 2: " + results.getT2());
});

2. exchangeToMono Yöntemi
Örnek
Şöyle yaparız
WebClient webClient = WebClient.create();
Mono<String> getHttpNonBlocking(String url) {
    return webClient
            .get()
            .uri(url)
            .exchangeToMono(cr -> cr.bodyToMono(String.class));
}
3. exchange + flatmap Yöntemi
Örnek
Elimizde şöyle bir kod olsun. Bu kod hem Mono hem de Flux dönebiliyor. Hem get() hem de put() işlemi için exchange() çağrısı yapıyor. Kod bir  spring bean. İskeleti şöyle
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;

@Component
public class WebClientHelper {

  private WebClient webClient;

  public WebClientHelper() {
    webClient = WebClient.create();
  }
}
Get ve Post için Mono dönen metodlar şöyle
public <T> Mono<T> performGetToMono(URI uri, MultiValueMap<String, String> params,
Class<? extends T> clazzResponse){
    return webClient.get()
        .uri(uriBuilder -> uriBuilder.scheme(uri.getScheme()).host(uri.getHost())
            .port(uri.getPort()).path(uri.getPath()).queryParams(params).build()
        )
        .exchange()
        .flatMap(clientResponse -> clientResponse.bodyToMono(clazzResponse));
}
public <T> Mono<T> performPostToMono(URI uri, Object requestBody,
Class<? extends T> clazzResponse){
    return webClient.post()
        .uri(uriBuilder -> uriBuilder.scheme(uri.getScheme()).host(uri.getHost())
            .port(uri.getPort()).path(uri.getPath()).build()
        )
        .body(BodyInserters.fromValue(requestBody))
        .exchange()
        .flatMap(clientResponse -> clientResponse.bodyToMono(clazzResponse));
}
Put ve Delete için Mono dönen metodlar şöyle
public <T> Mono<T> performPutToMono(URI uri, Object requestBody,
Class<? extends T> clazzResponse){
    return webClient.put()
        .uri(uriBuilder -> uriBuilder.scheme(uri.getScheme()).host(uri.getHost())
            .port(uri.getPort()).path(uri.getPath()).build()
        )
        .body(BodyInserters.fromValue(requestBody))
        .exchange()
        .flatMap(clientResponse -> clientResponse.bodyToMono(clazzResponse));
}
public <T> Mono<T> performDeleteToMono(URI uri, MultiValueMap<String, String> params,
Class<? extends T> clazzResponse){
    return webClient.delete()
        .uri(uriBuilder -> uriBuilder.scheme(uri.getScheme()).host(uri.getHost())
            .port(uri.getPort()).path(uri.getPath()).queryParams(params).build()
        )
        .exchange()
        .flatMap(clientResponse -> clientResponse.bodyToMono(clazzResponse));
}
Bu kodu kullanmak için şöyle yaparız
@RestController
@RequestMapping("/employeeClient")
public class EmployeeClientController {

  @Value("${employee.server.host}")
  private String employeeHost;

  @Autowired
  private WebClientHelper webClientHelper;

  @GetMapping("/{employeeId}")
  public Mono<EmployeeModel> getEmployeeById(@PathVariable("employeeId")
String employeeId){
    String url = ApiPaths.getEmployeePath(employeeHost) + "/" + employeeId;
    return webClientHelper.performGetToMono(URI.create(url), null, EmployeeModel.class);
  }
 
  @PostMapping
  public Mono<EmployeeModel> saveEmployee(@RequestBody EmployeeModel employeeModel){
    return webClientHelper.performPostToMono(URI.create(ApiPaths.getEmployeePath(
employeeHost)), employeeModel,
        EmployeeModel.class);
  }
...
}
Update ve Delete için şöyle yaparız
@PutMapping
public Mono<EmployeeModel> updateEmployee(@RequestBody EmployeeModel employeeModel){
  return webClientHelper
    .performPutToMono(URI.create(ApiPaths.getEmployeePath(employeeHost)), employeeModel,
      EmployeeModel.class);
  }
@DeleteMapping("/{id}")
public Mono<EmployeeModel> deleteEmployee(@PathVariable("id") Long employeeId){
  String url = ApiPaths.getEmployeePath(employeeHost) + "/" + employeeId;
  return webClientHelper.performDeleteToMono(URI.create(url),null, EmployeeModel.class);
}

SpringWebFlux Mono.fromCallable metodu

Örnek
Şöyle yaparız
return Mono.fromCallable(() ->
    getHttpBlocking("https://www.google.com"))
        .subscribeOn(Schedulers.boundedElastic())
Log çıktısı şöyledir
{"@timestamp":"2021-06-07T07:42:49.709-04:00","@version":"1",
"message":"querying google",
"logger_name":"net.kamradtfamily.blockingnono.Controller",
"thread_name":"reactor-http-nio-3",
"level":"INFO","level_value":20000}

{"@timestamp":"2021-06-07T07:42:49.939-04:00","@version":"1",
"message":"found content length 12991",
"logger_name":"net.kamradtfamily.blockingnono.Controller",
"thread_name":"boundedElastic-1",
"level":"INFO","level_value":20000}

SpringWebFlux Mono.just metodu

Giriş
Çağıran thread üzerinde çalışır. Thread değiştirmek için Mono.fromCallable().subscribeOn() kullanılır.

Örnek
Elimizde şöyle bir kod olsun
@Slf4j
@RestController
@RequestMapping("/")
public class Controller {
  RestTemplate restTemplate = new RestTemplate();
  @GetMapping("/blocking")
  public Mono<String> getFiles() {
    log.info("querying google");
    return Mono.just(getHttpBlocking("https://www.google.com"))
      .doOnNext(s -> 
           log.info("found content length {}", s.length()));
  }

  String getHttpBlocking(String url) {
    return restTemplate.getForObject(url, String.class);
  }
}
Log şöyledir. Burada Mono.just() çağrısının thread değiştirmediği görülebilir.
{"@timestamp":"2021-06-07T07:05:57.225-04:00","@version":"1",
"message":"querying google",
"logger_name":"net.kamradtfamily.blockingnono.Controller",
"thread_name":"reactor-http-nio-3",
"level":"INFO","level_value":20000}

{"@timestamp":"2021-06-07T07:05:57.640-04:00","@version":"1",
"message":"found content length 12962",
"logger_name":"net.kamradtfamily.blockingnono.Controller",
"thread_name":"reactor-http-nio-3",
"level":"INFO","level_value":20000}

29 Temmuz 2021 Perşembe

SpringCloud Stream @Output Anotasyonu - Kullanmayın

Giriş
@Output anotasyonu yerine StreamBridge sınıfı da kullanılabilir.

Örnek
Şeklen şöyle
application.yaml şöyle olsun
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
  cloud:
    stream:
      bindings:
        orderSubmissionOutput:
          destination: orderSubmitted.exchange
Şöyle yaparız. Böylece topic'e mesaj yazılır
public interface MessageChannels {

  static final String ORDER_SUBMISSION_OUTPUT = "orderSubmissionOutput";

  @Output (ORDER_SUBMISSION_OUTPUT)
  MessageChannel orderSubmissionChannel();
}


@MessagingGateway
public interface OrderSubmissionGateway {

  @Gateway(requestChannel = MessageChannels.ORDER_SUBMISSION_OUTPUT)
  void submitOrder(Order order);

}

@RestController
@RequestMapping("/orders")
public class OrderRestController {

  @Autowired
  private OrderSubmissionGateway orderSubmissionGateway;

  @PostMapping()
  public ResponseEntity<Order> submitOrder(@RequestBody @Valid Order order) {
    Order orderToBeSubmitted = order.withSubmissionDate(Instant.now());
    orderSubmissionGateway.submitOrder(orderToBeSubmitted);
    return ResponseEntity.ok(orderToBeSubmitted);
  }
}
Örnek - Producer
Şöyle yaparız. Burada @Output ile Source arayüzünün output isimli topic'e bir şey yazacağını belirtiriz.
public interface Source {

  String OUTPUT = "output";

  @Output(Source.OUTPUT)
  MessageChannel output();

}
application.properties şöyledir
cloud.stream:
 bindings.input:
   destination: payment-approval-topic
   group: payment-service-consumer
 bindings.output:
   destination: payment-notification-topic
   contentType: application/json
Kullanmak için şöyle yaparız
@EnableBinding({Source.class})
public class SubscriptionRequestsProducer {
  private final Source source;

  public SubscriptionRequestsProducer(Source source) {
    this.source = source;
  }

  public void requestApproval(Map<String, Object> subscriptionRequest) {
    source.output().send(MessageBuilder.withPayload(subscriptionRequest).build());
  }
}
Örnek - Processor
Şöyle yaparız
public interface MyProcessor {
   String INPUT = "myInput";

   @Input
   SubscribableChannel myInput();

   @Output("myOutput")
   MessageChannel anOutput();

   @Output
   MessageChannel anotherOutput();
}

SpringContext @Qualifier Anotasyonu Alternatifi - Strategy Pattern

Örnek - Generic Bean
Bir örnek burada
Bir örnek burada

Örnek - application.properties
Bu yöntemde iki tane bean yaratılıyor. Hangisinin inject edileceği application.properties ile seçilebiliyor.

Elimizde şöyle bir anotasyon olsun
package your.package;

@Retention(RetentionPolicy.RUNTIME)
public @interface InjectDynamicObject {
}
İki tane bean yaratalım
public interface Customer {
  public String getName();
}

@Component("customerOne")
public class CustomerOneImpl implements Customer {
  @Override
  public String getName() {
    return "Customer One";
  }
}

@Component("customerTwo")
public class CustomerTwoImpl implements Customer {
  @Override
  public String getName() {
    return "Customer Two";
  }
}
Çözüm 1 - Kolay Olan
Açıklaması şöyle
Spring injects the Map with Bean name as String and the Bean itself automagically.
Şöyle yaparız
public class CustomerServiceImpl {
    
  // We inject the customer implementations into a Map
  @Autowired
  private Map<String, Customer> dynamicCustomerWithMap;
    
  // This comes from the property file as a key for the Map
  @Value("${dynamic.object.name}")
  private String object;
  public Customer getDynamicCustomerWithMap() {
    return this.dynamicCustomerWithMap.get(object);
  }
}
Çözüm 2 - Zor Olan
Bu anotasyonu bir metodda kullanalım ve arayüzümüzü dönelim. Bu servis sınıfı @Autowire edilirse, aşağıda tanımlım olan @Around çalışır ve application.properties dosyasındaki tanımlı bean döner.
@Service
public class CustomerServiceImpl {
  private Customer dynamicCustomerWithAspect;
    
  @InjectDynamicObject
  public Customer getDynamicCustomerWithAspect() {
    return this.dynamicCustomerWithAspect;
  }
}
Bu metod için bir Aspect yazalım. 
@Component
@Aspect
public class DynamicObjectAspect {
  // This comes from the property file
  @Value("${dynamic.object.name}")
  private String object;
  @Autowired
  private ApplicationContext applicationContext;
    
  @Pointcut("execution(@com.lofi.springbean.dynamic.InjectDynamicObject * *(..))")
  public void beanAnnotatedWithInjectDynamicObject() {
  }
  @Around("beanAnnotatedWithInjectDynamicObject()")
  public Object adviceBeanAnnotatedWithInjectDynamicObject(ProceedingJoinPoint pjp)
throws Throwable {   
    pjp.proceed();
        
    // Create the bean or object depends on the property file  
    Object createdObject = applicationContext.getBean(object);
    return createdObject;
  }
}
Örnek - Metod Parametresi
Elimizde şöyle bir arayüz olsun
public enum GeolocationProvider {
  GOOGLE_MAP,
  GEO_BOUNDARIES
}

public interface GeolocationService {

  // to identify support for the provider
  GeolocationProvider getGeoProvider();

  //method will have the provider specific logic to find the latitude/longitude
  Result getLatLongByCity(String city);
}
Bu arayüzü gerçekleştiren iki bean olsun
@Service
public class GoogleMapLocationServiceImpl implements GeolocationService {

  @Override
  public GeolocationProvider getGeoProvider() {
    return GeolocationProvider.GOOGLE_MAP;
  }

  @Override
  public Result getLatLongByCity(String city) {
     ...
  }
}
@Service
public class GeoBoundariesLocationServiceImpl implements GeolocationService {


  @Override
  public GeolocationProvider getGeoProvider() {
    return GeolocationProvider.GEO_BOUNDARIES;
  }

  @Override
  public Result getLatLongByCity(String city) {
    ...
  }
}
Bu iki bean arasında seçim yapmak için metoda parametre geçeriz
@Component
public class GeoLocationProviderContext {

  private final EnumMap<GeolocationProvider, GeolocationService> map;

  @Autowired
  public GeoLocationProviderContext(Set<GeolocationService> geolocationServiceSet) {
    map = new EnumMap<>(GeolocationProvider.class);
    geolocationServiceSet.forEach(service -> map.put(service.getGeoProvider(), service));
  }
 
  public GeolocationService getService(GeolocationProvider provider) {
    return map.get(provider);
  }
}

@RestController
public class GeolocationController {

  @Autowired
  GeoLocationProviderContext geoLocationProviderContext;

  @GetMapping(value = "/coordinates/for/city/{cityname}/{provider}")
  public ResponseEntity<Result> getLocation(@PathParam(value = "cityname") String city,
                          @PathParam(value = "provider")GeolocationProvider provider) {
    GeolocationService service = geoLocationProviderContext.getService(provider);
    Result result = geolocationService.getLatLongByCity(city);
    return ResponseEntity.ok().body(result);
  }
}

27 Temmuz 2021 Salı

SpringBootLogging Log4J2 Kullanımı

Giriş
SpringBoot loglama için normalde logback kullanır. Açıklaması şöyle
If your spring boot project has been created from the starters project (which almost always is the case), it would have a transitive dependency on spring-boot-starter-logging, and you would not need to include any additional dependency to start with logging in your app. The default choice of framework that spring boot makes is Logback and Slf4j.
SpringBoot logging Ayarları yazısına bakabilirsiniz.

Log4J2 kullanmak için logback veya herhangi bir başka logger'ın path içinde olmaması lazım. Mesela lombok kütüphanesi slf4j kullanıyor.

Örnek
Şöyle yaparız
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <exclusions>
    <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-logging</artifactId>
    </exclusion>
  </exclusions>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
Örnek
Şöyle yaparız
</dependency><dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <exclusions>
    <exclusion>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-logging</artifactId>
    </exclusion>
    <exclusion>
     <groupId>ch.qos.logback</groupId>
     <artifactId>logback-classic</artifactId>
    </exclusion>
   </exclusions>
</dependency>
Log4J Konfigürasyon Dosyası
Açıklaması şöyle. Yani XML, JSON veya YAML dosyası kullanılabilir. src/main/resources altına yerleştirilebilir.
Spring Boot automatically configures Log4j if it finds a file named log4j2.xml or log4j2.json or log4j2.yaml in the classpath.

log4j2.xml Dosyası
xml dosyasını yine logback kullanımında olduğu gibi src/main/resources/log4j2.xml şeklinde yaratırız. 

Spring Profile
Açıklaması şöyle
The <SpringProfile> tag lets you include custom configuration based on the active Spring profiles. 
Örnek
Şöyle yaparız
<SpringProfile name=”dev | test”>
<! — configuration to be enabled when the “dev” or “test” profiles are active →
</SpringProfile>

<SpringProfile name=”staging”>
<! — configuration to be enabled when the “staging” profile is active →
</SpringProfile>

<SpringProfile name=”!production”>
<! — configuration to be enabled when the “production” profile is not active →
</SpringProfile>
Örnek
Şöyle yaparız. Burada dev ve prod diye iki profile var
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="logback-spring.xsd">

 <springProfile name="dev">
  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
   <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
   </encoder>
  </appender>
  <logger name="com.springboot.postgres" level="debug">
   <appender-ref ref="console" />
  </logger>
 </springProfile>

 <springProfile name="prod">
  <appender name="file" class="ch.qos.logback.core.FileAppender">
   <file>app.log</file>
   <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
   </encoder>
  </appender>
  <logger name="com.springboot.postgres" level="info">
   <appender-ref ref="file" />
  </logger>
 </springProfile>

</configuration>
Environment Properties Kullanımı
Açıklaması şöyle
You can look up properties from Spring Environment within your Log4j2 configuration. For instance, if configure the spring.application.name property in application.properties file, you can use it in your Log4j2 xml configuration with the following configuration. We are using the spring: prefix.
Örnek
Şöyle yaparız
<Properties> 
  <Property name=”applicationName”>${spring:spring.application.name}</property> 
</Properties>
Örnek
Şöyle yaparız. Burada logging.level ortam değişkeni kullanılıyor. Eğer tanımlı değilse varsayılan değer debug
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="logback-spring.xsd">

 <springProfile name="dev">
  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
   <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
   </encoder>
  </appender>
  <logger name="com.springboot.postgres" level="${logging.level:-debug}">
   <appender-ref ref="console" />
  </logger>
 </springProfile>
</configuration>
System Properties Kullanımı
Örnek
Şöyle yaparız. Burada sys:logging.level isimli system properties değişkeni kullanılıyor. Eğer tanımlı değilse varsayılan değer debug
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="logback-spring.xsd">

 <springProfile name="dev">
  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
   <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
   </encoder>
  </appender>
  <logger name="com.springboot.postgres" level="${sys:logging.level:-debug}">
   <appender-ref ref="console" />
  </logger>
 </springProfile>
</configuration>


SpringMVC @RequestHeader Anotasyonu

Giriş
Şu satırı dahil ederiz
import org.springframework.web.bind.annotation.RequestHeader;
Http Request alanlarını okumak içindir. 

Örnek
Şöyle yaparız
@RequestMapping("/fetchContentType")
public String getContentType(@RequestHeader("Content-Type") String contentType) { return "Content Type is: " + contentType; }
Örnek - Optional Headers
Şöyle yaparız
@RequestMapping("/optionalHeader")
public String getOptionalHeader(@RequestHeader(value = "optional-header", required = false) String headerValue) { return headerValue != null ? "Header value is: " + headerValue : "Header not present."; }
Örnek - Default Values For Optional Headers
Şöyle yaparız
@RequestMapping("/defaultHeaderValue")
public String getDefaultHeader(@RequestHeader(value = "X-Custom-Header", defaultValue = "default-value") String headerValue) { return "Header value is: " + headerValue; }
Örnek - Multiple Headers
Şöyle yaparız
@RequestMapping("/fetchHeaders")
public String getHeaders(@RequestHeader Map<String, String> headers) { return "Headers are: " + headers.toString(); }
Örnek - Type Conversion
Şöyle yaparız
@RequestMapping("/fetchDate")
public String getDate(@RequestHeader("Date") Date date) { return "Date header as a Date object: " + date.toString(); }

SpringCloud Gateway GatewayFilter Arayüzü - Belirli Bir Route İçin Kullanılır

Giriş
Şu satırı dahil ederiz
import org.springframework.cloud.gateway.filter.GatewayFilter;
GlobalFilter ve GatewayFilter benziyorlar. Açıklaması şöyle
Route filters allow the modification of the incoming HTTP request or outgoing HTTP response in some manner. Route filters are scoped to a particular route. Spring Cloud Gateway includes many built-in GatewayFilter Factories.
Örnek
Şöyle yaparız
//Apply filter for a specific route using spring inbuilt filter //"AddRequestHeader"
spring.cloud.gateway.routes[0].filters[0]=AddRequestHeader=first-request-header, 
  first-request-header-value
Örnek
Elimizde şöyle bir kod olsun
@RefreshScope
@Component
public class AuthenticationFilter implements GatewayFilter {

  @Autowired
  private RouterValidator routerValidator;//custom route validator
  @Autowired
  private JwtUtil jwtUtil;

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    ServerHttpRequest request = exchange.getRequest();

    if (routerValidator.isSecured.test(request)) {
      if (this.isAuthMissing(request))
        return this.onError(exchange, "Authorization header is missing in request",
HttpStatus.UNAUTHORIZED);

      final String token = this.getAuthHeader(request);

      if (jwtUtil.isInvalid(token))
        return this.onError(exchange, "Authorization header is invalid",
HttpStatus.UNAUTHORIZED);

      this.populateRequestWithHeaders(exchange, token);
    }
        return chain.filter(exchange);
    }

  ...
}
Yardımcı metodlar için şöyle yaparız
private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus) {
  ServerHttpResponse response = exchange.getResponse();
  response.setStatusCode(httpStatus);
  return response.setComplete();
}

private String getAuthHeader(ServerHttpRequest request) {
  return request.getHeaders().getOrEmpty("Authorization").get(0);
}

private boolean isAuthMissing(ServerHttpRequest request) {
  return !request.getHeaders().containsKey("Authorization");
}

private void populateRequestWithHeaders(ServerWebExchange exchange, String token) {
  Claims claims = jwtUtil.getAllClaimsFromToken(token);
  exchange.getRequest().mutate()
    .header("id", String.valueOf(claims.get("id")))
    .header("role", String.valueOf(claims.get("role")))
    .build();
}
RouterValidator kodu şöyledir
@Component
public class RouterValidator {

  public static final List<String> openApiEndpoints = List.of(
    "/auth/register",
    "/auth/login"
  );

  public Predicate<ServerHttpRequest> isSecured =
    request -> openApiEndpoints
      .stream()
      .noneMatch(uri -> request.getURI().getPath().contains(uri));
}
Filtreyi kullanmak için şöyle yaparız
@Configuration
@EnableHystrix
public class GatewayConfig {

  @Autowired
  AuthenticationFilter filter;

  @Bean
  public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
      .route("user-service", r -> r.path("/users/**")
        .filters(f -> f.filter(filter))
        .uri("lb://user-service"))

      .route("auth-service", r -> r.path("/auth/**")
        .filters(f -> f.filter(filter))
        .uri("lb://auth-service"))
      .build();
    }
}
Açıklaması şöyle 
- all requests that starts with /users/** should be routed to user service, and our custom JWT filter should be applied to each such request 
- all requests that starts with /auth/** should be routed to auth service, and our custom JWT filter should be applied to each such request too.
Örnek
Şöyle yaparız
public class CustomerRateLimitFilter extends
AbstractGatewayFilterFactory<CustomerRateLimitFilter.Config> {
  private final RateLimiter<?> rateLimiter = ...;
  private final KeyResolver keyResolver = ...;
       
  @Override
  public GatewayFilter apply(Config config) {
    return new OrderedGatewayFilter((exchange, chain) -> {

      Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
      return keyResolver.resolve(exchange).flatMap(key -> {
        if (StringUtil.isNullOrEmpty(key)) {
          return handleErrorResponse(exchange, HttpStatus.UNPROCESSABLE_ENTITY);
        }
        Mono<RateLimiter.Response> result = rateLimiter.isAllowed(route.getId(), key);
        return result.flatMap(response -> {

        response.getHeaders().forEach((k, v) -> exchange.getResponse().getHeaders()
.add(k, v));

          if (response.isAllowed()) {
            return chain.filter(exchange);
          }
          return handleErrorResponse(exchange, HttpStatus.TOO_MANY_REQUESTS);
        });
      });
    }, RATELIMIT_ORDER);
  }
 
}
Kullanmak için şöyle yaparız
spring:
  cloud:
    gateway:
      routes:
        - id: face-match-api-route
          uri: http://localhost:8800
          predicates:
            - Path=/face/**
          filters:
            - StripPrefix=1
            - name: CustomerRateLimitFilter
            - name: CustomerQuotaFilter

26 Temmuz 2021 Pazartesi

SpringRetry FixedBackOffPolicy Sınıfı

Giriş
Açıklaması şöyle.
BackOffPolicy is used to control back off between retry attempts. A FixedBackOffPolicy pauses for a fixed period of time before continuing.
Örnek
Şöyle yaparız
@Bean
public RetryTemplate retryTemplate() {

  RetryTemplate retryTemplate = new RetryTemplate();

  //BackOff Policy
  FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
  fixedBackOffPolicy.setBackOffPeriod(2000l);
  retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

  //Retry Policy
  SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
  retryPolicy.setMaxAttempts(2);
  retryTemplate.setRetryPolicy(retryPolicy);

  return retryTemplate;

}