28 Ağustos 2023 Pazartesi

SpringIntegration Redis JdbcLockRegistry Sınıfı

Örnek
Şöyle yaparız
@Bean
public DefaultLockRepository DefaultLockRepository(DataSource dataSource){
  return new DefaultLockRepository(dataSource);
}

@Bean
public JdbcLockRegistry jdbcLockRegistry(LockRepository lockRepository){
  return new JdbcLockRegistry(lockRepository);
}

SpringIntegration Distributed Lock

Giriş
Distributed Lock iki şekilde gerçekleştirilebilir
1. Redis
2. JDBC


Redis
Maven
Şu satırı dahil ederiz
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-integration</artifactId>
</dependency>

 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>
 <dependency>
  <groupId>org.springframework.integration</groupId>
  <artifactId>spring-integration-redis</artifactId>
 </dependency>
 <dependency>
  <groupId>io.lettuce</groupId>
  <artifactId>lettuce-core</artifactId>
 </dependency>
RedisLockRegistry sınıfı kullanılır

JDBC
Maven
Açıklaması şöyle
The JDBC version of the distributed lock needs the database to have some tables and indexes set up in order to work. If you do not set these up the first time you attempt to obtain the lock, a JDBC Exception will be thrown. The current collection of SQL files for this can be found in the Spring Integration JDBC github repo.

In the following example, Flyway runs the SQL script automatically.
Şu satırı dahil ederiz
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-integration</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.integration</groupId>
  <artifactId>spring-integration-jdbc</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
  <groupId>org.postgresql</groupId>
  <artifactId>postgresql</artifactId>
</dependency>

<dependency>
  <groupId>org.flywaydb</groupId>
  <artifactId>flyway-core</artifactId>
</dependency>
JdbcLockRegistry sınıfı kullanılır

Genel Kullanım
Elimizde şöyle bir kod  olsun
public interface LockService {
  String lock();
  void failLock();
  String properLock();
}

@Service
public class RedisLockService implements LockService{
  private static final String MY_LOCK_KEY = "someLockKey";
  private final LockRegistry lockRegistry;

  public RedisLockService(LockRegistry redisLockRegistry) {
    this.lockRegistry = redisLockRegistry;
  }
}

@Service
public class JDBCLockService implements LockService{
  private static final String MY_LOCK_KEY = "someLockKey";
  private final LockRegistry lockRegistry;

  public JDBCLockService(JdbcLockRegistry jdbcLockRegistry) {
    this.lockRegistry = jdbcLockRegistry;
  }
}
Şöyle yaparız. obtain() ile bir Lock nesnesi elde edilir. tryLock() ile bu kilitlenir.
a@Override
public String lock() {
  Lock lock = null;
  String returnVal = null;
  try {
    lock = lockRegistry.obtain(MY_LOCK_KEY);
  
    if (lock.tryLock()) {
      returnVal =  "lock successful";
    }
    else {
      returnVal = "lock unsuccessful";
    }
  } catch (Exception e) {
    ...
  } finally {
    if (lock != null) {
      lock.unlock();
    }
  }
  return returnVal;
}


SpringBoot Actuator SanitizingFunction Arayüzü

Giriş
Şu satırı dahil ederiz 
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
Örnek
Şöyle yaparız. "test.password" alanı * karakteri ile gizlenir
@Component
public class ActuatorSanitizer implements SanitizingFunction {

  private static final String[] REGEX_PARTS = {"*", "$", "^", "+"};
  private static final Set<String> DEFAULT_KEYS_TO_SANITIZE = 
    Set.of("password","secret");
  private final List<Pattern> keysToSanitize = new ArrayList<>();

  public ActuatorSanitizer(@Value("${management.endpoint.additionalKeysToSanitize:}")
    List<String> additionalKeys) {
    addKeysToSanitize(DEFAULT_KEYS_TO_SANITIZE);
    addKeysToSanitize(additionalKeys);
  }

  @Override
  public SanitizableData apply(SanitizableData data) {
    if (data.getValue() == null) {
      return data;
    }

    for (Pattern pattern : keysToSanitize) {
      if (pattern.matcher(data.getKey()).matches()) {
        return data.withValue(SanitizableData.SANITIZED_VALUE);
      }
     }
     return data;
  }

  private void addKeysToSanitize(Collection<String> keysToSanitize) {
    for (String key : keysToSanitize) {
      this.keysToSanitize.add(getPattern(key));
    }
  }

  private Pattern getPattern(String value) {
    if (isRegex(value)) {
      return Pattern.compile(value, Pattern.CASE_INSENSITIVE);
    }
    return Pattern.compile(".*" + value + "$", Pattern.CASE_INSENSITIVE);
  }

  private boolean isRegex(String value) {
    for (String part : REGEX_PARTS) {
      if (value.contains(part)) {
        return true;
      }
    }
    return false;
  }
}

24 Ağustos 2023 Perşembe

SpringWebFlux Flux.limitRate metodu - Backpressure İçindir

Giriş
Her seferinde kaynaktan en fazla kaç tane nesne çekileceğini belirtir

Örnek
Şöyle yaparız
// Simulate a data source emitting a continuous stream of data at a high rate
Flux<Integer> dataSource = Flux.range(1, Integer.MAX_VALUE);
  
// Process the data with back pressure using limitRate operator
dataSource
  .limitRate(10) // Control the number of elements emitted per request (back pressure)
  .doOnNext(data -> { // Simulate processing delay
    try {
      Thread.sleep(100);
    }
    catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Processed: " + data);
  })
  .subscribe();
Açıklaması şöyle
In this example, the Flux.range(1, Integer.MAX_VALUE) simulates a continuous stream of data. The limitRate(10) operator specifies that the data processing should be limited to 10 elements per request, effectively controlling the back pressure.

As a result, you’ll notice that the data processing rate is limited, and the doOnNext method will print  "Processed: <data>" with a slight delay between each processed element

SpringWebFlux Flux.switchIfEmpty metodu

Giriş
Mono.switchIfEmpty ile kardeştir. Flux boş ise switchIfEmpty() ile belirtilen sonucu döner

Örnek
Şöyle yaparız
Flux<Integer> dataFlux = Flux.fromIterable(List.of()).
  switchIfEmpty(Mono.just(0)) 
  .collectList();

23 Ağustos 2023 Çarşamba

SpringAsync @Async Anotasyonu - Sonuç Tipi

Giriş
@Async metod void veya Future veya CompletableFuture dönmelidir. CompletableFuture dönen bir örnek buradaAsyncResult dönmeye gerek yok

Örnek - Polling
Bir örnek burada

20 Ağustos 2023 Pazar

SpringWebFlux WebTestClient Sınıfı

Giriş
Açıklaması şöyle
Reactive Spring provides WebTestClient to write down integration tests for API endpoints.
get metodu
Örnek
Şöyle yaparız
@Autowired
private WebTestClient webTestClient; @Test void find_all_patients(){ long patientCount = patientRepository.findAll().count().block(); webTestClient.get().uri("/v1/patients") .exchange().expectStatus().isEqualTo(HttpStatus.ACCEPTED) .expectBody() .jsonPath("$").isArray() .jsonPath("$.size()").isEqualTo(patientCount); }
expectStatus metodu
isCreated()isOk()isNotFound() gibi sonuçlar döner

Örnek
Şöyle yaparız
@Test
public void testGetAllUsers() {
    webTestClient.get().uri("/users")
            .exchange()
            .expectStatus().isOk()
            .expectBodyList(User.class);
}

@Test
public void testGetUserById() {
    webTestClient.get().uri("/users/{id}", "user-id")
            .exchange()
            .expectStatus().isOk()
            .expectBody(User.class);
}

@Test
public void testCreateUser() {
    User newUser = new User("new-user-id", "New User");

    webTestClient.post().uri("/users")
            .bodyValue(newUser)
            .exchange()
            .expectStatus().isCreated()
            .expectBody(User.class)
            .isEqualTo(newUser);
}
Örnek
Şöyle yaparız
@Test
public void testGetUserById_NotFound() {
  webTestClient.get().uri("/users/nonexistent-id")
    .exchange()
    .expectStatus().isNotFound();
}

SpringWebFlux StepVerifier Sınıfı

Giriş
Açıklaması şöyle
When testing reactive components, ensure you cover error scenarios using StepVerifier to verify the behavior of your reactive streams in response to different types of errors.
Örnek
Şöyle yaparız
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;

public class ReactiveStreamTest {
  @Test
  public void testFlux() {
    Flux<Integer> numbers = Flux.just(1, 2, 3, 4, 5);

    StepVerifier.create(numbers)
      .expectNext(1, 2, 3, 4, 5)
      .verifyComplete();
  }
  @Test
  public void testTransformations() {
    Flux<Integer> numbers = Flux.just(1, 2, 3, 4, 5);

    StepVerifier.create(numbers
      .filter(number -> number % 2 == 0)
      .map(evenNumber -> evenNumber * 2)
    )
    .expectNext(4, 8)
    .verifyComplete();
  }
  @Test
  public void testWithError() {
    Flux<Integer> numbers = Flux.just(1, 2, 3)
      .concatWith(Flux.error(new RuntimeException("Oops! An error occurred.")));

    StepVerifier.create(numbers)
      .expectNext(1, 2, 3)
      .expectError(RuntimeException.class)
      .verify();
  }
}

SpringWebFlux Mono.onErrorResume metodu - Fallback Value

Giriş
Açıklaması şöyle
Project Reactor provides several operators to manage errors effectively within reactive streams:

1. onErrorResume and onErrorReturn: These operators allow you to provide fallback values or alternative streams in case of an error. This can help prevent the entire stream from failing and provide a more graceful degradation.
2. doOnError: This operator lets you execute specific actions when an error occurs, such as logging the error or cleaning up resources. It doesn't interfere with the error propagation itself.
3. retry and retryWhen: These operators enable you to automatically retry an operation a specified number of times or based on a certain condition. This can be helpful for transient errors.
Kısaca onErrorResume exception'ı loglar ve varsayılan bir sonuç döndürür

Örnek
Şöyle yaparız
public Mono<User> getUserById(String id) {
  return userRepository.findById(id)
    .onErrorResume(throwable -> {
      log.error("Error occurred while fetching user by id: {}", id, throwable);
      return Mono.just(new User("default", "Default User"));
    });
}
Örnek
Şöyle yaparız
webClient.get()
    .uri("/endpoint")
    .retrieve()
    .bodyToMono(String.class)
    .doOnError(e -> log.error("Error occurred", e))
    .onErrorResume(e -> Mono.just("Fallback value"));

SpringWebFlux Flux.merge metodu

Giriş
Açıklaması şöyle
The merge operator combines elements from multiple streams into a single stream, interleaving elements as they arrive. The concat operator, on the other hand, concatenates the streams one after another.
Örnek
Şöyle yaparız
Flux<Integer> flux1 = Flux.just(1, 2, 3);
Flux<Integer> flux2 = Flux.just(4, 5, 6);
Flux<Integer> mergedFlux = Flux.merge(flux1, flux2); // interleaved
Flux<Integer> concatenatedFlux = Flux.concat(flux1, flux2); // in order


SpringWebFlux Flux.zip metodu

Giriş
Açıklaması şöyle
The zip operator combines elements from two or more reactive streams into pairs, tuples, or other custom objects. It's useful when you need to process elements from multiple streams together.
Örnek
Şöyle yaparız
Flux<Integer> numbers = Flux.just(1, 2, 3);
Flux<String> letters = Flux.just("A", "B", "C");
Flux<String> combined = Flux.zip(numbers, letters, (number, letter) -> number + letter);
Örnek
Şöyle yaparız
Flux<String> endpoint1 = webClient.get()
  .uri("/endpoint1").retrieve().bodyToMono(String.class);
Flux<String> endpoint2 = webClient.get()
  .uri("/endpoint2").retrieve().bodyToMono(String.class);

Flux<String> result = Flux.zip(
  endpoint1, 
  endpoint2, 
  (res1, res2) -> res1 + " " + res2);

SpringData JPA @EntityGraph.type FETCH + @NamedEntityGraph Kullanımı - N+1 Select Problem İçindir

Giriş
Açıklaması şöyle. Yani  @NamedEntityGraph.attributeNodes anotasyonu ile belirtilenler EAGER yüklenir. Geri kalan her şey LAZY yüklenir.
..., attributes that are specified by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are treated as FetchType.LAZY

Örnek - @NamedEntityGraph.attributeNodes 
Şöyle yaparız. Burada Publication sınıfı ve ona ait Article sınıfları EAGER yükleniyor.
@Entity
@Table(name = "publication")
@NamedEntityGraph(name="publication-articles-graph",
  attributeNodes = @NamedAttributedNode(value ="articles"))
public class Publication {
  ...
  @OneToMany(cascade = CascadeType.ALL)
  @JoinColumn(name = "publicationId")
  private List<Article> articled;
}

public interface PublicationRepository extends JpaRepository<Publication,String> {
  @EntityGraph(type = EntityGraph.EntityGraphType.FETCH,
    value = "publication-articles-graph")
  List<Publication> findByCategory(String category);
}
Eğer @NamedEntityGraph kullanmak istemiyorsak şöyle yaparız. Burada  @EntityGraph.attributePaths kullanılıyor
public interface PublicationRepository extends JpaRepository<Publication,String> { @EntityGraph(type = EntityGraph.EntityGraphType.FETCH, attributePaths = "articles") List<Publication> findByCategory(String category); }
Çıkan SQL şöyledir
SELECT * FROM publication LEFT OUTER JOIN article ON publication.publication_id = article.publication_id WHERE publication.category = 'technology'
Örnek
Şöyle yaparız. Burada Book ve ona ait Author nesneleri EAGER yüklenir
@Entity @NamedEntityGraph( name = "Book.author", attributeNodes = @NamedAttributeNode("author") ) public class Book { @ManyToOne private Author author; // ... } @Repository public interface BookRepository extends JpaRepository<Book, Long> { @EntityGraph("Book.author") List<Book> findAll(); }

14 Ağustos 2023 Pazartesi

SpringBoot Actuator - Metrics Endpoint - DataDog

Maven
Maven için şu satırı dahil ederiz. Bunun amacı SpringBoot Metrics Actuator tarafından oluşturulan metriklerin DataDog' un okuyabileceği bir formata çevirmek
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-registry-datadog</artifactId>
</dependency>
DatadogMeterRegistry Sınıfı

Kullanım
Örnek
Şöyle yaparız.
@SpringBootApplication
public class SpringBootDatadogMetricsApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringBootDatadogMetricsApplication.class, args);
  }

  @Bean
  public MeterRegistry meterRegistry() {
    // Configure Datadog-specific settings
    DatadogConfig datadogConfig = new DatadogConfig() {
      @Override
      public String apiKey() {
        return "YOUR_DATADOG_API_KEY";
      }

      @Override
      public String get(String key) {
        return null; // Use the default values for other configuration options
      }
    };

    // Create and return a DatadogMeterRegistry
    return new DatadogMeterRegistry(datadogConfig, Clock.SYSTEM);
  }

  // Define a sample counter bean
  @Bean
  public Counter sampleCounter(MeterRegistry meterRegistry) {
    return Counter.builder("custom.counter")
      .description("A custom counter metric")
      .register(meterRegistry);
  }
}
Counter nesnesini kullanmak için şöyle yaparız
final Counter sampleCounter;  // Constructor Injection

...
double valueToRecord = // calculated value

sampleCounter.record(valueToRecord);
Eğer Counter nesnesini dinamik olarak yaratmak istersek şöyle yaparız. Burada counter nesnesine tag veriliyor
@Service
public class SaleService {

  final MeterRegistry meterRegistry;
  
  public void makeSales(...) {
    ...
    Product product = ...

    Counter salesCounter = Counter.builder("sales.dollarAmount")
           .description("sales")
           .tag("productCategory", product.category, "customerAge", customer.age)
           .register(meterRegistry);
    
    salesCounter.record(product.price);

  }
}
Açıklaması şöyle
In Micrometer, tags are key-value pairs that provide additional context to your metrics. They help you organize and categorize your metrics, making it easier to filter and query them. Tags are especially useful when pushing metrics to monitoring systems like Datadog, as they allow you to create more detailed and customized visualizations and alerts.

For Example, let’s say you want to record time series metrics on successful amounts of sales of your products. You want to be able to make an analysis based on item categories as well as the age of the customer. While there may be a finite number of categories, you need to be able to support the introduction of a new category without any code change. In that case, you need to be able to construct theCounter object on the fly instead of registering it as a bean. 
config metodu
Açıklaması şöyle
Sometimes, you need to track some metrics based on the instance of the server the metric is coming from. These are usually infrastructure-related resources such as CPU, memory, and database connections. Let’s say you want to keep track of database connection usage. You need to pinpoint which cluster of services and which server in the cluster has high usage of database connections. This can be made possible by using common tags.

Örnek
Şöyle yaparız.
@SpringBootApplication
public class SpringBootDatadogMetricsApplication {
  @Value ..
  String environment;

  @Value ..
  String serviceName;

  @Bean
  public MeterRegistry meterRegistry() {
    // Configure Datadog-specific settings
    DatadogConfig datadogConfig = new DatadogConfig() {
      @Override
      public String apiKey() {
        return "YOUR_DATADOG_API_KEY";
      }
      @Override
      public String get(String key) {
        return null; // Use the default values for other configuration options
      }
    };

    // Create and return a DatadogMeterRegistry and add common tags
    DatadogMeterRegistry registry =
      new DatadogMeterRegistry(datadogConfig, Clock.SYSTEM);
    Config config = registry.config();       

    HashSet<Tags> tags = new HashSet<>();
    tags.add(Tags.of("environment", environment));
    tags.add(Tags.of("serviceName", serviceName));

    config.commonTags(tags);
    return registry;
  }
}
EC2 Instance Metadata to Tags ve Adding Metadata for Tagging on ECS Fargate örnekleri de burada


9 Ağustos 2023 Çarşamba

SpringData Redis LettuceClientConfiguration Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
readFrom metodu
Açıklaması şöyle. Okuma işleminin nerede yapılacağını belirtir.
All ReadFrom settings except MASTER may return stale data because replicas replication is asynchronous and requires some delay. You need to ensure that your application can tolerate stale data.

Setting                 Description 
MASTER                 Default mode. Read from the current master node.

MASTER_PREFERRED         Read from the master, but if it is unavailable, read from replica nodes.

REPLICA         Read from replica nodes.

REPLICA_PREFERRED        Read from the replica nodes, but if none is unavailable, read from the master.

LOWEST_LATENCY Read from any node of the cluster with the lowest latency.

ANY                         Read from any node of the cluster.

ANY_REPLICA         Read from any replica of the cluster.
Örnek
Şöyle yaparız
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
    .commandTimeout(redisCommandTimeout)
    .readFrom(ReadFrom.REPLICA_PREFERRED)
    .build();

SpringNative Buildpacks

Giriş
Açıklaması şöyle
Spring Boot includes buildpack support for native images directly for Maven . This means we can just type a single command and quickly get a sensible image into our locally running Docker daemon. The resulting image doesn’t contain a JVM, instead the native image is compiled statically. This leads to smaller images. There are 3 types of buildpack image available for use:
1. paketobuildpacks/builder:tiny
2. paketobuildpacks/builder:base
3. paketobuildpacks/builder:full
spring-boot-starter-parent Kullanıyorsak
Açıklaması şöyle
The spring-boot-starter-parent declares a native profile that configures the executions that need to run in order to create a native image. GraalVM Native Support dependency generate this in pom. You can activate profiles using the -P flag on the command line. 
Şöyle yaparız
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.1.0</version>
  <relativePath/> <!-- lookup parent from repository -->
 </parent>
Sonra şöyle yaparız
$ mvn -Pnative spring-boot:build-image
Yapılandırılan Docker image'i çalıştırmak için şöyle yaparız
docker run --rm -p 8080:8080 native:0.0.1-SNAPSHOT
spring-boot-starter-parent Kullanmıyorsak
Açıklaması şöyle
If you don’t want to use spring-boot-starter-parent you’ll need to configure executions for the process-aot goal from Spring Boot’s plugin and the add-reachability-metadata goal from the Native Build Tools plugin.
NoClassDefFoundError Hataları
Bu durumda reflection-config.json dosyasını tekrar üretmek gerekir



2 Ağustos 2023 Çarşamba

SpringKafka Consumer @RetryableTopic Anotasyonu -Integration Test

Örnek
Test iskeleti şöyle
import org.springframework.kafka.test.utils.ContainerTestUtils;
@Slf4j @EnableKafka @SpringBootTest @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) @EmbeddedKafka(partitions = 1, brokerProperties = {"listeners=" + "${kafka.broker-properties.listeners}", "port=" + "${kafka.broker-properties.port}"}, controlledShutdown = true, topics = {"test", "test-retry-0", "test-retry-1", "test-retry-2", "test-dlt"} ) @ActiveProfiles("test") class CustomEventConsumerIntegrationTest { @Autowired private KafkaListenerEndpointRegistry registry; @Autowired private EmbeddedKafkaBroker embeddedKafkaBroker; @Autowired private KafkaTemplate<String, CustomEvent> testKafkaTemplate; @Value("${retry.attempts}") private int maxRetries; @MockBean CustomEventHandler customEventHandler; ... }
Açıklaması şöyle
When using an embedded Kafka broker, it is important to mention the topics to be created. They will not be created automatically. In this case, we are creating 4 topics, namely:

"test", "test-retry-0", "test-retry-1", "test-dlt"
Her test için şöyle yaparız
import org.springframework.kafka.test.utils.ContainerTestUtils;
@BeforeEach void setUp() { //wait for partitions to be assigned registry.getListenerContainers().forEach(container -> ContainerTestUtils.waitForAssignment(container, embeddedKafkaBroker.getPartitionsPerTopic())); log.info("Partitioned Assigned. Starting tests"); } @AfterEach void tearDown() { embeddedKafkaBroker.destroy(); }
Daha sonra çeşitli testler burada anlatılıyor. Kod ise burada. Retry olmadığını test eden bir test şöyle
@Test
void test_should_not_retry_if_consumption_is_successful()  {
  CustomEvent event = new CustomEvent("Hello");
  // GIVEN
  doNothing().when(customEventHandler).handleEvent(any(CustomEvent.class));

  // WHEN
  testKafkaTemplate.send("test", event).get();

  // THEN
  verify(customEventHandler, timeout(2000).times(1)).handleEvent(any(CustomEvent.class));
  verify(customEventHandler, timeout(2000).times(0))
    .handleEventFromDlt(any(CustomEvent.class));
}




1 Ağustos 2023 Salı

SpringKafka Consumer KafkaListenerEndpoint Arayüzü - Dinamik Olarak Listener Tanımlamak İçindir

Şu satırı dahil ederiz
import org.springframework.kafka.config.KafkaListenerEndpointRegistry;
KafkaListenerEndpoint için açıklaması şöyle
The KafkaListenerEndpoint class is a class that stores information to define a kafka consumer, including information regarding the consumer id, the listened topics, the consumer group id, the consumer class, the methods that used to process messages, and so on. Because KafkaListenerEndpoint is an interface, we can use one of its implementation classes, one of them is the MethodKafkaListenerEndpoint class. This MethodKafkaListenerEndpoint class is also used to define kafka consumers when we use the @KafkaListener annotation.

MethodKafkaListenerEndpoint Sınıfı
Kalıtım şöyle
KafkaListenerEndpoint 
  MethodKafkaListenerEndpoint 

Örnek
Elimizde şöyle bir kod olsun
@Service
public class MyKafkaTemplateListener implements MessageListener<String, String> {

  @Override
  public void onMessage(ConsumerRecord<String, String> record) {
    System.out.println("RECORD PROCESSING: " + record);
  }
}
Şöyle yaparız
@Service
public class KafkaListenerCreator {
  String kafkaGroupId = "kafkaGroupId";
  String kafkaListenerId = "kafkaListenerId-";
  static AtomicLong endpointIdIndex = new AtomicLong(1);

  private KafkaListenerEndpoint createKafkaListenerEndpoint(String topic) {
    MethodKafkaListenerEndpoint<String, String> kafkaListenerEndpoint =
      createDefaultMethodKafkaListenerEndpoint(topic);
    kafkaListenerEndpoint.setBean(new MyKafkaTemplateListener());
    try {
      kafkaListenerEndpoint.setMethod(KafkaTemplateListener.class.getMethod("onMessage",
        ConsumerRecord.class));
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Attempt to call a non-existent method " + e);
    }
    return kafkaListenerEndpoint;
  }

  private MethodKafkaListenerEndpoint<String, String>
    createDefaultMethodKafkaListenerEndpoint(String topic) {
    MethodKafkaListenerEndpoint<String, String> kafkaListenerEndpoint = 
      new MethodKafkaListenerEndpoint<>();
    kafkaListenerEndpoint.setId(generateListenerId());
    kafkaListenerEndpoint.setGroupId(kafkaGroupId);
    kafkaListenerEndpoint.setAutoStartup(true);
    kafkaListenerEndpoint.setTopics(topic);
    kafkaListenerEndpoint.setMessageHandlerMethodFactory(
      new DefaultMessageHandlerMethodFactory());
    return kafkaListenerEndpoint;
  }
  private String generateListenerId() {
    return kafkaGeneralListenerEndpointId + endpointIdIndex.getAndIncrement();
  }
}
Örnek
Şöyle yaparız
import org.springframework.kafka.config.MethodKafkaListenerEndpoint;
import org.springframework.messaging.handler.annotation.support
  .DefaultMessageHandlerMethodFactory;
import org.apache.kafka.clients.consumer.ConsumerRecord;

MethodKafkaListenerEndpoint<String, String> kafkaListenerEndpoint = 
  new MethodKafkaListenerEndpoint<>();
kafkaListenerEndpoint.setId("...");
kafkaListenerEndpoint.setGroupId("...");
kafkaListenerEndpoint.setAutoStartup(true);
kafkaListenerEndpoint.setTopics("...");
kafkaListenerEndpoint.setMessageHandlerMethodFactory(new
  DefaultMessageHandlerMethodFactory());
kafkaListenerEndpoint.setBean(new MyMessageListener());
kafkaListenerEndpoint.setMethod(MyMessageListener.class.getMethod("onMessage",
  ConsumerRecord.class));

import org.springframework.kafka.listener.MessageListener;
class MyMessageListener implements MessageListener<String, String> {

  @Override
  public void onMessage(ConsumerRecord<String, String> record) {
    ...
  }
}

SpringCloud AWS Systems Manager - AWS Systems Manager'dan Bilgileri Alır

Giriş
SpringCloud AWS Secrets Manager kullanımına çok benziyor

Maven
Şu satırı dahil ederiz
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.awspring.cloud</groupId>
      <artifactId>spring-cloud-aws-dependencies</artifactId>
      <version>3.0.1</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependency>
  <groupId>io.awspring.cloud</groupId>
  <artifactId>spring-cloud-aws-starter-parameter-store</artifactId>
</dependency>
IAM Permissions
Açıklaması şöyle
For access, IAM permissions can be set up. The required Spring Cloud AWS permission is: ssm:GetParameters
Şöyle yaparız
Sample IAM policy granting access to Parameter Store:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "ssm:GetParametersByPath",
            "Resource": "*"
        }
    ]
}
application.yaml Dosyası
Açıklaması şöyle
spring.config.import property is used to fetch parameters from AWS Parameter Store and add them to Spring’s environment properties.
Örnek
Şöyle yaparız
spring:
  profiles:
    active: dev
  application:
    name: my-demo-boot
  # AWS parameter store configuration
  cloud:
    aws:
      credentials:
        access-key: <your-access-key>
        secret-key: <your-secret-key>
        profile:
          name: default
      region:
        static: us-east-2
  config:
    import:
      - aws-parameterstore:/config/application_${spring.profiles.active}/

# actuator configuration
management:
  endpoints:
    enabled-by-default: false
    web:
      exposure:
        include: 'health, env'
  endpoint:
    health:
      enabled: true
      show-details: always
    env:
      enabled: true

Spring 3 İçin SpringNative @RegisterReflectionForBinding Anotasyonu

Giriş
Açıklaması şöyle
Registering classes for which you want to use reflection is just as easy, you either use @RegisterReflectionForBinding annotation or you can do the same through the RuntimeHintsRegistrar with more flexible configuration:

Moreover, you can use the @RegisterReflectionForBinding annotation anywhere (services, methods and etc.), but we decided to use only the RuntimeHintsRegistrar to contain all the AOT configurations in one place and not search through the code.
Örnek
Jackson için şöyle yaparız
@RegisterReflectionForBinding({MyClass.class, MyClass2.class})
Örnek
Şöyle yaparız
@Configuration
@RegisterReflectionForBinding({CustomMessage.class, CustomMessage.Status.class})
@ImportRuntimeHints(AppConfiguration.AppRuntimeHintsRegistrar.class)
public class AppConfiguration {

  public static class AppRuntimeHintsRegistrar implements RuntimeHintsRegistrar {

   @Override
   public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
     hints.reflection()
       .registerType(
         CustomMessage.class,
         PUBLIC_FIELDS, INVOKE_PUBLIC_METHODS, INVOKE_PUBLIC_CONSTRUCTORS
         ).registerType(
           CustomMessage.Status.class,
           PUBLIC_FIELDS, INVOKE_PUBLIC_METHODS, INVOKE_PUBLIC_CONSTRUCTORS
         );
      }
  }
}

Spring 3 İçin SpringNative @ImportRuntimeHints Anotasyonu

Giriş
Açıklaması şöyle
In Spring Native (as we did in Quarkus), we need to specify some classes for reflection, serialization, proxy usage, etc. Spring calls all of these <hints>. The problem is that the GraalVM, at compilation time, cannot recognize every class in our project that must be used for reflection at runtime. For this reason, these frameworks (Spring and Quarkus) have annotations that we can use con classes to specify reflection at compilation time to GraalVM.
Örnek
Şöyle yaparız
@SpringBootApplication
@ImportRuntimeHints(LdapServiceApplication.MyRuntimeHints.class)
public class LdapServiceApplication {

 public static void main(String[] args) {
  SpringApplication.run(LdapServiceApplication.class, args);
 }

 @Bean
 public RestTemplate restTemplate() {
  return new RestTemplate();
 }

 @PostConstruct
 void postConstruct() {
  System.setProperty("javax.net.ssl.trustStore", 
    System.getProperty("javax.net.ssl.trustStore"));
  System.setProperty("javax.net.ssl.trustStorePassword", 
    System.getProperty("javax.net.ssl.trustStorePassword"));
 }

 static class MyRuntimeHints implements RuntimeHintsRegistrar {

  @Override
  public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
   // Register serialization
   hints.serialization().registerType(HashMap.class).registerType(LinkedList.class);
   hints.reflection().registerType(TypeReference.of("javax.net.ssl.SSLSocketFactory"),
     builder -> builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
   hints.resources().registerPattern("db/migration/*.sql");
  }
}
Örnek
Şöyle yaparız
import org.postgresql.util.PGobject;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;

public class PostgresRuntimeHints implements RuntimeHintsRegistrar {

    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        hints.reflection().registerType(PGobject.class, MemberCategory.values());
    }
}


import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.jdbcjobstore.JobStoreSupport;
import org.quartz.impl.jdbcjobstore.JobStoreTX;
import org.quartz.impl.jdbcjobstore.PostgreSQLDelegate;
import org.quartz.utils.HikariCpPoolingConnectionProvider;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;

public class QuartzRuntimeHints implements RuntimeHintsRegistrar {

  @Override
  public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
    hints.reflection().registerType(JobStoreSupport.class, MemberCategory.values());
    hints.reflection().registerType(JobStoreTX.class, MemberCategory.values());
    hints.reflection().registerType(StdSchedulerFactory.class, MemberCategory.values());
    hints.reflection().registerType(HikariCpPoolingConnectionProvider.class, MemberCategory.values());
    hints.reflection().registerType(PostgreSQLDelegate.class, MemberCategory.values());
  }
}