30 Aralık 2022 Cuma

SpringCloud OpenFeign Unit Test

Giriş
1. FakeFeignConfiguration yaratılır. Bu sınıf içinde FakeRibbonConfiguration ayağa kaldırılır. 
2. FakeRibbonConfiguration  da çağrıları @LocalServerPort ile testin kullandığı porta yönlendirir.
3. Test içinde FakeMessagingRestService ayağa kaldırılır

Örnek
Açıklaması şöyle
Spring uses OpenFeign under the hood to build the client component that does the rest-based service call and uses Netflix Ribbon to provide client-side load balancing to the provisioned client component.
Örnek
Elimizde FeignClient kodu olsun
@FeignClient(name = "messagingRestClient", path = "/messaging")
public interface MessagingRestClient {
  @GetMapping(params = {"name"})
  Message getMessage(@RequestParam("name") final String name);

  @PostMapping(params = {"name"})
  Message setMessage(@RequestParam("name") final String name, 
                     @RequestBody final Message message);

}
Şöyle yaparız. FakeFeignConfiguration ve feign çağrılarına cevap verecek FakeMessagingRestService test içinde ayağa kaldırılır. FakeFeignConfiguration içinde localhost'a işaret eden bir Ribbon client ayarı yapılır. Böylece tüm feign istekleri localhost'a gönderilir. RibbonClient içinde com.netflix.loadbalancer.Server ve com.netflix.loadbalancer.ServerList sınıfları kullanılıyor
@SpringBootTest(classes = {MessagingRestClientBootTests.FakeFeignConfiguration.class,
                           MessagingRestClientBootTests.FakeMessagingRestService.class},
                webEnvironment = WebEnvironment.RANDOM_PORT)
class MessagingRestClientBootTests {
  @RestController
  @RequestMapping(path = "/messaging")
  static class FakeMessagingRestService {
    @GetMapping(params = {"name"},produces = "application/json")
    public String getMessage(@RequestParam("name") final String name) {
      assertThat(name).isEqualTo("Foo");
      return "{\"text\":\"Hello, Foo\"}";
    }
    @PostMapping(params = {"name"}, produces = "application/json")
    public String setMessage(@RequestParam("name") final String name,
                             @RequestBody final String message) throws Exception {
      assertThat(name).isEqualTo("Foo");
      JSONAssert.assertEquals(message, "{ \"text\":\"Hello\" }", false);
      return "{\"text\":\"Hi, Foo\"}";
    }
  }
  @Configuration(proxyBeanMethods = false)
  static class FakeRibbonConfiguration {
    @LocalServerPort int port;
    @Bean
    public ServerList<Server> serverList() {
      return new StaticServerList<>(new Server("localhost", port));
    }
  }
  @Configuration(proxyBeanMethods = false)
  @EnableFeignClients(clients = MessagingRestClient.class)
  @EnableAutoConfiguration
  @RibbonClient(name = "messagingRestClient",
      configuration = MessagingRestClientBootTests.FakeRibbonConfiguration.class)
  static class FakeFeignConfiguration {}
}
Aynı sınıfta testleri şöyle yaparız
@SpringBootTest(
    classes = {
      MessagingRestClientBootTests.FakeFeignConfiguration.class,
      MessagingRestClientBootTests.FakeMessagingRestService.class
    },
    webEnvironment = WebEnvironment.RANDOM_PORT)
class MessagingRestClientBootTests {

  @Autowired MessagingRestClient client;

  @Test
  public void getMessage() {
    final Message response = client.getMessage("Foo");
    assertThat(response.getText()).isEqualTo("Hello, Foo");
  }

  @Test
  public void setMessage() {
    final Message message = new Message();
    message.setText("Hello");
    final Message response = client.setMessage("Foo", message);
    assertThat(response.getText()).isEqualTo("Hi, Foo");
  }
  ...
}
Örnek
Elimizde FeignClient kodu olsun
@FeignClient(name = "test")
public interface FeignAPI {
  @RequestMapping(value = "hello")
  String hello();
}
Şöyle yaparız. Burada ilk örnekten farklı olarak RestController ayrı bir sınıf değil, FeignConfig içinde tanımlı. Aslında aynı kapıya çıkıyor.
@SpringBootTest(classes = FeignAPITest.FeignConfig.class, 
  webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class FeignAPITest {
  @Autowired
  FeignAPI feignAPI;
  @Configuration
  static class RibbonConfig {
    @LocalServerPort
    int port;
    @Bean
    public ServerList<Server> serverList() {
      return new StaticServerList<>(new Server("127.0.0.1", port));
    }
  }
  @EnableFeignClients(clients = FeignAPI.class)
  @RestController
  @Configuration
  @EnableAutoConfiguration
  @RibbonClient(name = "test", configuration = FeignAPITest.RibbonConfig.class)
  static class FeignConfig {

    @RequestMapping(value = "hello")
    public String testFeign() {
      return "success";
    }
  }
  @Test
  public void testFeign() {
    assertThat(this.feignAPI.hello()).isEqualTo("success");
  }
}

SpringBatch ItemStream Arayüzü

Giriş
Açıklaması şöyle
Both ItemReaders and ItemWriters serve their individual purposes well, but there is a common concern among both of them that necessitates another interface. In general, as part of the scope of a batch job, readers and writers need to be opened, closed, and require a mechanism for persisting state. The ItemStream interface serves that purpose
Kodu şöyle
public interface ItemStream {
  void open(ExecutionContext executionContext) throws ItemStreamException;
  void update(ExecutionContext executionContext) throws ItemStreamException;
  void close() throws ItemStreamException;
}


SpringBatch ItemProcessor Arayüzü

Giriş
Açıklaması şöyle
ItemProcessor class accepts an object and processes the data and returns the processed data as another object.

29 Aralık 2022 Perşembe

SpringBoot TomcatProtocolHandlerCustomizer Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
Örnek - Prjoject Loom 
Şöyle yaparız
@Configuration public class VirtualThreadConfig {

@Bean
  public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {

   return protocolHandler -> {
      protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
    };
  }
}

SpringCache Redis RedisCacheManager Sınıfı

Giriş
Şu satırı dahil ederiz.
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
Çoğunlukla bu sınıfı yaratmak için RedisCacheManager.builder() çağrısı ile RedisCacheManagerBuilder kullanılıyor

constructor - RedisCacheWriter + RedisCacheConfiguration

Örnek
Şöyle yaparız
@Bean("cache-manager") public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate) { var redisCacheWriter = RedisCacheWriter .nonLockingRedisCacheWriter(Objects.requireNonNull( redisTemplate.getConnectionFactory())); var redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer( redisTemplate.getValueSerializer())); return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration); }

27 Aralık 2022 Salı

SpringCloud AWS SQS @SqsListener Anotasyonu

Giriş
Şu satırı dahil ederiz
import org.springframework.cloud.aws.messaging.listener.annotation.SqsListener;
Açıklaması şöyle
As we will use Spring to build the microservice we could use Spring Cloud AWS. It is very easy to build an SQS listener using this library. With little to no configurations, one can create the listener method just by annotation it with @SqsListener annotation.

But this library is limited. The maximum amount of messages that can be fetched at once cant be more than 10. Although it allows parallel processing, receiving up to 10 messages doesn't allow saving messages at once to the database.

Because it receives messages, creates multiple parallel threads, and each thread calls the listener method passing one message as an argument.

deletionPolicy Alanı
Şöyle yaparız
import com.aws.sqs.model.Message; import org.springframework.cloud.aws.messaging.listener.SqsMessageDeletionPolicy;
import org.springframework.cloud.aws.messaging.listener.annotation.SqsListener;

@RestController
@RequestMapping(value = "/sqs")
public class SqsController {
  //queue name
  private static final String QUEUE = "my-queue";
...
  // @SqsListener listens to the message from the queue.
  @SqsListener(value = QUEUE, deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
  public void getMessageFromSqs(Message message, @Header("MessageId") String messageId) {
    logger.info("Received message= {} with messageId= {}", message.toString(), messageId);
  }
}

26 Aralık 2022 Pazartesi

SpringSecurity @EnableMethodSecurity Anotasyonu

Giriş
SpringBoot 3 ile geliyor. Eski @EnableGlobalMethodSecurity ile aynı şeyi yapıyor. Method level security sağlar. Security için @Secured, @RoleAllowed, @PreAuthorize anotasyonları kullanılır
Örnek
Şöyle yaparız
// This is Deprecated
@EnableGlobalMethodSecurity(securedEnabled = true
  jsr250Enabled = true, prePostEnabled = true)
public class SecurityConfig{}

// Use Enable Method Security, prePost is Enabled by Default
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig{}



SpringWebFlux DefaultPartHttpMessageReader Sınıfı

Giriş
multipart/form-data cevapları okuyabilmemizi sağlar. Açıklaması şöyle
It reads the "multipart/form-data" requests to a stream of Parts
content metodu
Örnek
Şöyle yaparız. Burada block() metodu kullanıldığı için setStreaming(false) yapılıyor
final var partReader = new DefaultPartHttpMessageReader();
partReader.setStreaming(false); 

WebClient webClient = WebClient.builder().build();
ResponseEntity<Flux<Part>> request = webClient
  .get()
  .uri("...")
  .accept(MediaType.MULTIPART_MIXED)
  .retrieve()
  .toEntityFlux((inputMessage, context) ->
    partReader
      .read(ResolvableType.forType(byte[].class), inputMessage, Map.of()))
  .block();

byte[] image = null;
    
List<Part> parts = request.getBody().collectList().block();

for (Part part : parts) {
  // access individual parts here
  System.out.println(part.headers());
  if (part.headers().get("Content-Type").get(0).equals("image/jpeg")) {
    image = DataBufferUtils.join(part.content())
      .map(dataBuffer -> {
        byte[] bytes = new byte[dataBuffer.readableByteCount()];
        dataBuffer.read(bytes);
        DataBufferUtils.release(dataBuffer);
        return bytes;
      }).block();
  }
}

return image;
Açıklaması şöyle
part.headers() will give you the headers and part.content() will give you the actual content in DataBuffer format.

We iterate the list of parts, and determine which one is an image by peeking into the headers. Once we get that, we use the DataBufferUtils , a helper class to convert the buffer into byte array format.

And that’s it, extracting images/files from multipart data is now a walk in the park thanks to Spring Flux !

SpringWebFlux WebClient Flux Dönüşümü

1. bodyToFlux metodu
Örnek
Şöyle yaparız
WebClient webClient = WebClient.create();
webClient.get()
    .uri("http://example.com/stream")
    .accept(MediaType.TEXT_EVENT_STREAM) // for Server-Sent Events (SSE)
    .retrieve()
    .bodyToFlux(String.class) // convert the response body to a Flux
    .subscribe(data -> System.out.println("Received: " + data));

2. toEntityFlux metodu
Örnek
Şöyle yaparız
WebClient webClient = WebClient.builder().build();
ResponseEntity<Flux<Part>> request = webClient
  .get()
  .uri("...")
  .accept(MediaType.MULTIPART_MIXED)
  .retrieve()
  .toEntityFlux((inputMessage, context) ->
    partReader
      .read(ResolvableType.forType(byte[].class), inputMessage, Map.of()))
  .block();
Örnek
Şöyle yaparız.
@GetMapping(path = "/streaming", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@ResponseBody
public Flux<Something> streamSomething() {
  return WebClient.create()
    .get().uri("http://example.org/resource")
    .retrieve().bodyToFlux(Something.class)
    .delaySubscription(Duration.ofSeconds(5))
    .repeat();
}
Örnek
Şöyle yaparız.
WebClient webClient = WebClient.builder().baseUrl(baseUrl).build();

webClient.post().uri(uri)
  .contentType(MediaType.APPLICATION_JSON_UTF8)
  .accept(MediaType.APPLICATION_JSON_UTF8)
  .header(HttpHeaders.AUTHORIZATION, "Basic " + Base64Utils
  .encodeToString((plainCreds)
  .getBytes(Charset.defaultCharset())))
  .body(BodyInserters.fromObject(body)).retrieve()
  .bodyToFlux(EmployeeInfo.class)
  .doOnError(throwable -> {
    ...
  }).subscribe(new Consumer<EmployeeInfo>() {
    @Override
    public void accept(EmployeeInfo employeeInfo) {
      ...
    }
}); 
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();
  }
}
Flux dönen metodlar şöyle. Burada uri() metodu önemli. Gönderilecek uri'ye verilecek parametreler burada belirtiliyor.
public <T> Flux<T> performGetToFlux(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()
        .flatMapMany(clientResponse -> clientResponse.bodyToFlux(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
  public Flux<EmployeeModel> getEmployeeList(){
    return webClientHelper.performGetToFlux(URI.create(...), null, EmployeeModel.class);
  }
}