13 Temmuz 2023 Perşembe

SpringKafka Consumer RetryTopicConfigurationSupport Sınıfı

Giriş
Açıklaması şöyle
When Using @RetryableTopic for methods annotated with KafkaListener, provide a @Configuration class extends RetryTopicConfigurationSupport .
Örnek
Şöyle yaparız
@Configuration
@RequiredArgsConstructor
@EnableScheduling
@Slf4j
public class KafkaConfig extends RetryTopicConfigurationSupport {

  @Override
  protected void configureBlockingRetries(BlockingRetriesConfigurer blockingRetries) {
    blockingRetries
      .retryOn(IOException.class)
      .backOff(new FixedBackOff(5000, 3));
  }
}


SpringKafka Consumer @DltHandler Anotasyonu

Örnek
Şöyle yaparız.
import org.springframework.kafka.annotation.DltHandler;

@Slf4j
@RequiredArgsConstructor
@Component
public class UpdateItemConsumer {

...
  @DltHandler
  public void dlt(String data, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
    log.error("Event from topic "+topic+" is dead lettered - event:" + data);
  }
}
Örnek
Şöyle yaparız.
@DltHandler
public void processMessage(OrderEvent message) {
    log.error("DltHandler processMessage = {}", message);
}

5 Temmuz 2023 Çarşamba

SpringWebFlux Flux.onErrorReturn metodu - Exception Olursa Yeni Bir Sonuç Döner

Giriş
Flux kapatılır

Örnek
Şöyle yaparız
Flux<String> stringFlux = Flux.just("Hello", "World", "from", "IntelliJ IDEA")
    .map(s -> {
        if (s.equals("World")) throw new RuntimeException("An error occurred");
        else return s.toUpperCase();
    });

stringFlux
    .onErrorReturn("Error occurred in the stream.")
    .subscribe(System.out::println);
Çıktı şöyle
HELLO
Error occurred in the stream.
Örnek
Şöyle yaparız
Flux<Integer> numbers = Flux.just(1, 2, 3, 4, 5)
  .concatWith(Flux.error(new RuntimeException("Oops! An error occurred.")))
  .map(number -> 10 / (number - 3)) // This will cause an ArithmeticException

  .doOnError(throwable -> System.err.println("Error occurred: " + throwable.getMessage()))

  .onErrorReturn(-1); // Provide a fallback value in case of an error

numbers.subscribe(
  value -> System.out.println("Received: " + value),
  error -> System.err.println("Subscriber error: " + error.getMessage())
);

SpringDoc OpenAPI OpenAPI Sınıfı

addSecurityItem metodu
Örnek
Şöyle yaparız
@Bean
 public OpenAPI customizeOpenAPI() {
  String securitySchemeName = "bearerAuth";
  return new OpenAPI()
    .addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
          .components(
              new Components()
                  .addSecuritySchemes(securitySchemeName,
                      new SecurityScheme()
                          .name(securitySchemeName)
                          .type(SecurityScheme.Type.HTTP)
                          .scheme("bearer")
                          .bearerFormat("JWT")
                  )
          );
 }
addServerItems metodu
Örnek
Şöyle yaparız
@Bean
public OpenAPI customOpenAPI() { return new OpenAPI() .addServersItem(new Server().url("https://myserver.com")) .addServersItem(new Server().url("https://google.com")) .components( new Components() .addSecuritySchemes("basicScheme",new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme("basic")) ) .info( new Info() .title("SpringShop API") .version("0.0.1") .license(new License().name("Apache 2.0").url("http://springdoc.org")) ); }
Örnek
Şöyle yaparız
@Bean
 public OpenAPI openApiInformation() {
  Server localServer = new Server()
                      .url("http://localhost:8080")
                      .description("Localhost Server URL");

  Contact contact = new Contact()
                    .email("niket.agrawal90@gmail.com")
                    .name("Niket Agrawal");

  Info info = new Info()
              .contact(contact)
              .description("Spring Boot 3 + Open API 3")
              .summary("Demo of Spring Boot 3 & Open API 3 Integration")
              .title("Spring Boot 3 + Open API 3")
              .version("V1.0.0")
              .license(new License().name("Apache 2.0").url("http://springdoc.org"));

  return new OpenAPI().info(info).addServersItem(localServer);
}
http://localhost:8080/swagger-ui/index.html adresindeki ekran görüntüsü şöyle



externalDocs metodu
Örnek
Şöyle yaparız
@Bean
 public OpenAPI springOpenAPI() {
  return new OpenAPI()
    .info(new Info()
      .title("Micro service")
      .description("APIs for Test Console service")
	  .version("1.0")
      .license(new License().name("Dev Team").url("https://github.com")))
    .externalDocs(new ExternalDocumentation()
	  .description("Test Documentation")
	  .url("https://github.com"));
 }
info metodu
Örnek
Şöyle yaparız
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;


@Configuration
class OpenApiConfig {

  @Bean
  public OpenAPI customOpenAPI(@Value("${application-description}"
                               String appDesciption, 
                               @Value("${application-version}"
                               String appVersion) {
    return new OpenAPI()
      .info(new Info()
        .title("sample application API")
.version(appVersion)
.description(appDesciption)
.termsOfService("http://swagger.io/terms/")
.license(new License().
          name("Apache 2.0").
          url("http://springdoc.org")));
  }
}

4 Temmuz 2023 Salı

SpringAOP PerformanceMonitorInterceptor Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.aop.interceptor.PerformanceMonitorInterceptor;
Açıklaması şöyle
When you enable the “PerformanceMonitorInterceptor,” it quietly observes your code and collects essential information, such as the execution time of different methods. It keeps a record of how much time is spent in each method, which is incredibly helpful in identifying bottlenecks or areas where your code might be slowing down.

Having access to this information allows you to analyze and optimize your code to make it faster and more efficient. For example, if you discover that a particular method is taking too much time to execute, you can focus on optimizing that specific part of your code to improve overall performance.

Moreover, the “PerformanceMonitorInterceptor” enables you to measure the performance of your code across multiple requests or interactions. This broader perspective helps you understand how your application is performing over time, rather than relying on isolated measurements.
Maven
Şu satırı dahil ederiz
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

AbstractMonitoringInterceptor sınıfından kalıtır. Loglama ayarları için şöyle yaparız
logging.level.dev.knowledgecafe=TRACE
logging.level.org.springframework.aop.interceptor.PerformanceMonitorInterceptor=TRACE

Örnek
Şöyle yaparız
@Configuration
@EnableAspectJAutoProxy
public class AopConfiguration {
  @Pointcut("execution(public String foo.EmployeeService.getFullName(..)))")
  public void monitor() { }

  @Bean
  public PerformanceMonitorInterceptor performanceMonitorInterceptor() {
    return new PerformanceMonitorInterceptor(true);
  }

  @Bean
  public Advisor performanceMonitorAdvisor() {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    pointcut.setExpression("dev.knowledgecafe.performance_trace.AopConfiguration.monitor()");
    return new DefaultPointcutAdvisor(pointcut, performanceMonitorInterceptor());
  }
}

27 Haziran 2023 Salı

SpringMVC Declarative REST Client - OpenFeign Yerine Kullanılır

Giriş
OpenFeign yerine kullanılır. Açıklaması şöyle
HTTP Interface Client introduced in Spring 6. The HTTP Interface Client enables to definition a declarative way for HTTP services using Java interfaces. Under the hood, Spring generates a proxy class that implements the interface and performs exchanges.
Bağımlılıklar
Açıklaması şöyle
All necessary components are in the spring-web module, that happens to be a transitive dependency for the spring-boot-starter-web or the spring-boot-starter-webflux modules. However, in practice the WebFlux dependency is always required at the moment due to the HttpServiceProxyFactory for generating the clients. 
Açıklaması şöyle
Why do you need Spring Reactive Web dependencies
When creating the project above, the dependency of Spring Reactive Web was introduced, and the WebClient type was used when creating the service object of the proxy. This is because HTTP Interface currently only has built-in implementation of WebClient, which belongs to the category of Reactive Web. Spring will launch a RestTemplate-based implementation in subsequent versions.
Anotasyonlar 
Anotasyonlar şöyle
@HttpExchange
@PutExchange
@DeleteExchange

baseUrl metodu
İsteğin gönderileceği URL adresini belirtir
Örnek
Şöyle yaparız
public interface CatFactsClient {
  @GetExchange(value = "/facts")
  List<Fact> getFacts();
}

@Configuration
public class CatFactsClientConfig {

  @Bean
  public CatFactsClient catFactsClient() {
    WebClient client = WebClient.builder()
      .baseUrl("https://cat-fact.herokuapp.com")
      .build();
    HttpServiceProxyFactory factory = HttpServiceProxyFactory
      .builder(WebClientAdapter.forClient(client))
      .build();
    return factory.createClient(CatFactsClient.class);
  }
}

@Service
public class MyService {

  @Autowired
  private CatFactsClient catFactsClient;

  public List<Fact> fetchCatFacts() {
    return catFactsClient.getFacts();
  }
}
clientConnector metodu
Örnek - WebClient Connection reset by peer error
Gönderilen istekler için keep-alive seçeneğini kapatmak gerekir. Şöyle yaparız
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
import reactor.netty.http.client.HttpClient;


@Configuration
public class HttpProxyConfiguration {

  @Value("${tracker.url}")
  private String trackerUrl;

  @Bean
  TrackerClient trackerClient(WebClient.Builder builder) {
    var httpClient = HttpClient.newConnection().keepAlive(false); // Here
    var reactorClientHttpConnector = new ReactorClientHttpConnector(httpClient);

    var wc = builder.baseUrl(trackerUrl)
      .clientConnector(reactorClientHttpConnector)
      .build();

    var wca = WebClientAdapter.forClient(wc);
    return HttpServiceProxyFactory.builder()
      .clientAdapter(wca)
      .build()
      .createClient(TrackerClient.class);
  }
}

defaultStatusHandler metodu
HttpStatus.NOT_FOUND, HttpStatusCode::is5xxServerError gibi durumlarda ne yapılacağını belirtir

Örnek
Elimizde şöyle bir kod olsun
@HttpExchange(
        url = "/characters",
        accept = MediaType.APPLICATION_JSON_VALUE)
public interface CharacterClient {

  @GetExchange
  List<CharacterResponse> getByName(@RequestParam String lastName);

  @GetExchange("/{id}")
  Optional<CharacterResponse> getById(@PathVariable long id);

  @PutExchange(contentType = MediaType.APPLICATION_JSON_VALUE)
  CharacterResponse addCharacter(@RequestBody AddCharacterRequest request);

  @DeleteExchange("/{id}")
  void deleteById(@PathVariable long id);
}
Bu arayüzden Spring kod üretir. Daha sonra bu kodu WebClient ile birleştirmek gerekir. Şöyle yaparız
@Configuration
public class CharacterClientConfig {

  @Bean
  public CharacterClient characterClient(CharacterClientProperties properties) {
    WebClient webClient = WebClient.builder()
      .baseUrl(properties.getUrl())
      .defaultStatusHandler(
        httpStatusCode -> HttpStatus.NOT_FOUND == httpStatusCode,
        response -> Mono.empty())
      .defaultStatusHandler(
        HttpStatusCode::is5xxServerError,
        response -> Mono
          .error(new ExternalCommunicationException(response.statusCode().value())))
      .build();

    return HttpServiceProxyFactory
      .builder(WebClientAdapter.forClient(webClient))
      .build()
      .createClient(CharacterClient.class);
  }
}

@Data
@Component
@ConfigurationProperties("character-client")
public class CharacterClientProperties {
    private String url;
}
Açıklaması şöyle. Yani şimdilik HttpServiceProxyFactory + WebClientAdapter + WebClient kodunu elle yazmak gerekiyor.
Currently, unlike OpenFeign, the client is not yet supplied via auto-configuration in a Spring Boot setup (kindly track Support declarative HTTP clients #31337 for that matter). Therefore, we build a WebClient ourselves and create a declarative HTTP client from it by using the createClient method from HttpServiceProxyFactory. This is some kind of boilerplate code, but I am quite confident that the Spring Boot guys will come up with a nice solution to simplify this further. Up until this point, you will need such a bean definition for each declarative HTTP client in your application.
Kullanmak için şöyle yaparız
CharacterResponse brandon = characterClient
    .addCharacter(new AddCharacterRequest("Brandon", "Stark"));

List<CharacterResponse> starks = characterClient.getByName("Stark");

Optional<CharacterResponse> eddardStark = characterClient.getById(1);

Optional<CharacterResponse> unknown = characterClient.getById(1337L); // empty

characterClient.deleteById(brandon.id());
Eğer Sadece Web Client Kullansaydık şöyle yaparız. Bu da karışık kodlar demek
public List<CharacterResponse> getByName(String lastName) {
  return webClient
    .get()
    .uri(uriBuilder -> uriBuilder
                    .path("/characters")
                    .queryParam("lastName", "{lastName}")
                    .build(lastName))
    .retrieve()
    .toEntityList(CharacterResponse.class)
    .block()
    .getBody();
}