31 Ocak 2021 Pazar

CustomizableThreadFactory Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
Thread'e ön ek (prefix) verebilmeyi sağlar

28 Ocak 2021 Perşembe

SpringBoot Actuator - Info Endpoint

Giriş
Info Actuator nesnesini etkinleştirmek için şöyle yaparız. Böylece contributor nesnelerde bilgiler derlenmeye başlar. 
management.endpoint.info.enabled: true
Ancak bu endpoint HTTP için açık değil. Açmak için şöyle de yaparız
management.endpoints.web.exposure.include=info,health,loggers
Bu aşamadan sonra şu adrese giderse info bilgilerini görebiliriz.
http://localhost:8080/actuator/info
Contributors
info adresinde gösterilen bilgileri sağlayan şeylere contributors deniliyor. Bunlar şöyle
build
Enabled by default: true
Goal: Exposes build information.
Requires: you to generate build information
Configuration property: management.info.build.enabled

env
Enabled by default: false (since Spring Boot 2.6. For the older Spring Boot version, this contributor is enabled by default!)
Goal: Exposes any property from the SpringEnvironment .
Required: to specify properties with names that start with info.*
in you application.yml or application.properties
Configuration property: management.info.env.enabled

git
Enabled by default: true
Goal: exposes git information.
Requires: you to generate git information
Configuration property: management.info.git.enabled

java
Enabled by default: false
Goal: exposes Java runtime information.
Configuration property: management.info.java.enabled
Bu contributor'ların da etkin olup olmadığını kontrol etmek mümkün.

Git Contributor
Açıklaması şöyle
Who never needed to quickly know which branch is deployed in the development server to know if a feature of bug-fix is already available?
Açıklaması şöyle
If the branch name, commit id, and timestamp are not enough, you can enable displaying more information by setting the property below in your application properties:

management.info.git.mode=full

This will add a lot more information, like commit message, commit author, and ahead/behind commits information.

Örnek
Şu satırı dahil ederiz
<plugin>
 <groupId>io.github.git-commit-id</groupId>
 <artifactId>git-commit-id-maven-plugin</artifactId>
</plugin>
Çıktısı şöyle
{
  "git": {
    "branch": "main",
    "commit": {
      "id": "d2035e1",
      "time": "2023-03-01T18:55:58Z"
    }
  }
}
Build Contributor
Örnek
Şöyle yaparız
<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <executions>
      <execution>
        <goals>
          <goal>build-info</goal>
        </goals>
        <configuration>
          <additionalProperties>
            <java.version>${java.version}</java.version>
            <some.custom.property>some value</some.custom.property>
          </additionalProperties>
        </goals>
      </execution>
  </executions>
</plugin>
Açıklaması şöyle. spring-boot-maven-plugin projeyi build etmek için kullanılınca ismi build-info.properties olan özel bir dosya oluşturur
When build-info of spring-boot-maven-plugin is run, it generates a property file containing all the build information. By default, it is located at ${project.build.outputDirectory}/META-INF/build-info.properties, but you can customize it by providing outputFile parameter.
Eğer bu dosya varsa Spring ismi BuildProperties olan özel bir bean yaratır. Açıklaması şöyle.
When Spring detects there is this file on the classpath, it creates BuildProperties bean unless it is explicitly declared.
Env Contributor
Örnek
Örneğin env contributor Spring2.6'dan sonra etkin gelmiyor. Etkinleştirmek için şöyle yaparız
management.info.env.enabled
Örnek 
Şöyle yaparız
management.info.env.enabled=true
info.app.name=Spring Boot Actuator Dashboard
info.app.description=Spring Boot backend to demonstrate actuator APIs
info.app.version=1.0
 http://localhost:8080/actuator/info adresine gidersek çıktı olarak şunu alırız
{
  "app": {
    "name": "Spring Boot Actuator Dashboard",
    "description": "Spring Boot backend to demonstrate actuator APIs",
    "version": "1.0"
  }
}
Örnek 
Şöyle yaparız. Burada farklı olarak info.java-vendor giriliyor
## Configuring info endpoint
info.app.name=Spring Sample Application
info.app.description=This is my first spring boot application
info.app.version=1.0.0
info.java-vendor = ${java.specification.vendor}
Örnek
Şöyle yaparız. Burada farklı olarak info.application.spring-cloud-version giriliyor
spring:
  application:
    name: example-app

info:
  application:
    name: ${spring.application.name}
    description: Very cool Spring Boot application
    version: '@project.version@'
    spring-cloud-version: '@spring-cloud.version@'
    spring-boot-version: '@project.parent.version@'
Görmek için şöyle yaparız
curl http://localhost:8080/actuator/info | jq
{
  "application": {
    "name": "example-app",
    "description": "Very cool Spring Boot application",
    "version": "0.0.1-SNAPSHOT",
    "spring-cloud-version": "2020.0.4",
    "spring-boot-version": "2.5.7"
  }
}

InfoContributor 
Örnek
Elimizde şöyle bir kod olsun. Burada kendimiz InfoContributor olan bir bean yarattık
@Component
public class DemoInfoContributor implements InfoContributor {
    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("demo",
            Collections.singletonMap("info-timestamp", LocalDateTime.now().toString()));
    }
}
Şimdi bir istek gönderelim
GET http://127.0.0.1:8080/actuator/info
Çıktı olarak şunu alırız
{
  "app": {
    "name": "actuator-test",
    "description": "null"
  },
  "git": {
    "branch": "master",
    "commit": {
      "id": "ff889ec",
      "time": "2020-10-29T22:36:04Z"
    }
  },
  "build": {
    "artifact": "actuator-test",
    "name": "actuator-test",
    "time": "2020-10-30T21:53:59.493Z",
    "version": "0.0.1-SNAPSHOT",
    "group": "com.relaximus"
  },
  "demo": {
    "info-timestamp": "2020-10-30T23:23:07.241981"
  }
}


SpringBoot Actuator - Loggers Endpoint

Giriş
Açıklaması şöyle
Shows the configuration of loggers and modify them
Açıklaması şöyle. Log seviyesini değiştirmek için Spring Boot Admin kullanılabilir.
It provides a feature to view and update the logs level.
Bu endpoint HTTP için açık değil. Açmak için şöyle yaparız
management.endpoints.web.exposure.include=info,health,loggers
Örnek - GET
loglama ayarlarını görmek için şöyle yaparız
GET http://127.0.0.1:8080/actuator/loggers/com.relaximus.actuatortest
Çıktı olarak şunu alırız
{
  "configuredLevel": "INFO",
  "effectiveLevel": "INFO"
}
loglama ayarını değiştirmek için şöyle yaparız
POST http://127.0.0.1:8080/actuator/loggers/com.relaximus.actuatortest
Content-Type: application/vnd.spring-boot.actuator.v3+json

{
  "configuredLevel": "DEBUG"
}
Örnek - POST
loglama ayarlarını değiştirmek için şöyle yaparız.
`curl -X "POST" "http://localhost:8080/loggers/<class or package name>" -H "Content- 
Type: application/json; charset=utf-8" -d $'{
"configuredLevel": "ERROR"
}'`

SpringKafka Producer DefaultKafkaProducerFactory - KafkaTemplate Yaratmak İçin Gerekir

Giriş
Şu satırı dahil ederiz
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
DefaultKafkaProducerFactory nesnesi, KafkaTemplate nesnesine geçilir. KafkaTemplate ile mesaj gönderebilmek için gerekir. ProducerConfig sınıfında bazı sabitler tanımlıdır. Bunların açıklaması şöyle
BOOTSTRAP_SERVERS_CONFIG: This is the key for the Kafka bootstrap servers configuration.

KEY_SERIALIZER_CLASS_CONFIG: Serializer class for Producer Factory key

VALUE_SERIALIZER_CLASS_CONFIG: Serializer class for Producer Factory Value
BOOTSTRAP_SERVERS_CONFIG Kafka sunucusunun adresini belirtir.

Örnek
BOOTSTRAP_SERVERS_CONFIG + serializers. Şöyle yaparız
@Configuration
public class KafkaProducerConfig {
    
  @Bean
  public ProducerFactory producerFactory(KafkaProperties kafkaProperties) {
    HashMap<String, Object> properties = new HashMap<>();
    properties.putAll(kafkaProperties.buildProducerProperties());
    properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
    StringSerializer keySerializer = new StringSerializer();
    StringSerializer valueSerializer = new StringSerializer();
    return new DefaultKafkaProducerFactory<>(properties, keySerializer, valueSerializer);
  }
    
  @Bean
  public KafkaTemplate kafkaTemplate(ProducerFactory producerFactory) {
    KafkaTemplate kafkaTemplate = new KafkaTemplate<>(producerFactory);
    return kafkaTemplate;
  }
}
Örnek
BOOTSTRAP_SERVERS_CONFIG + serializers. Şöyle yaparız
@Configuration
public class KafkaProducerConfig {
    
  @Bean
  public ProducerFactory producerFactory(KafkaProperties kafkaProperties) {
    HashMap<String, Object> properties = new HashMap<>();
    properties.putAll(kafkaProperties.buildProducerProperties());
    properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
    StringSerializer keySerializer = new StringSerializer();
    StringSerializer valueSerializer = new StringSerializer();
    return new DefaultKafkaProducerFactory<>(properties, keySerializer, valueSerializer);
  }
    
  @Bean
  public KafkaTemplate kafkaTemplate(ProducerFactory producerFactory) {
    KafkaTemplate kafkaTemplate = new KafkaTemplate<>(producerFactory);
    return kafkaTemplate;
  }
}
Örnek
KEY_SERIALIZER_CLASS_CONFIG + VALUE_SERIALIZER_CLASS_CONFIG. Şöyle yaparız
@Configuration
public class KafkaProducerConfig {
  @Autowired
  private KafkaProperties kafkaProperties;

  @Bean
  public Map<String, Object> producerConfigs() {
    Map<String, Object> props = 
      new HashMap<>(kafkaProperties.buildProducerProperties());
    props.put(org.apache.kafka.clients.producer.ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
      StringSerializer.class);
    props.put(org.apache.kafka.clients.producer.ProducerConfig
.VALUE_SERIALIZER_CLASS_CONFIG,
      JsonSerializer.class);
    return props;
  }

  @Bean
  public ProducerFactory<String, Object> producerFactory() {
    return new DefaultKafkaProducerFactory<>(producerConfigs());
  }

  @Bean
  public KafkaTemplate<String, Object> kafkaTemplate() {
    return new KafkaTemplate<>(producerFactory());
  }
}
Örnek - Avro
Avro için SCHEMA_REGISTRY_URL_CONFIG ve diğer bir sürü parametre. Şöyle yaparız
@Bean
public Map<String, Object> producerConfigs() {
  Map<String, Object> props = new HashMap<>();
  props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
  props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
  props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class);
  props.put(ProducerConfig.CLIENT_ID_CONFIG, "kafka-producer");
  props.put(AbstractKafkaSchemaSerDeConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistry);
  props.put(ProducerConfig.ACKS_CONFIG, "all");
  props.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, "10000");
  props.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, "10000");
  props.put(ProducerConfig.RETRIES_CONFIG, "0");
  return props;
}
Örnek - Avro
Şöyle yaparız
@Bean
public ProducerFactory<String, PaymentSent> producerFactory(
  @Value("${kafka.bootstrap-servers}") final String bootstrapServers, 
  @Value("${kafka.schema.registry.url}") final String schemaRegistryUrl) {

  Map<String, Object> config = new HashMap<>();
  config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);

  config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
  config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class);
  config.put(KafkaAvroSerializerConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistryUrl);
  config.put(KafkaAvroSerializerConfig.AUTO_REGISTER_SCHEMAS, false);

  return new DefaultKafkaProducerFactory<>(config);
}

SpringData R2DBC application.properties

Giriş
Açıklaması şöyle
The critical properties are spring.r2dbc.url and spring.r2dbc.username and spring.r2dbc.password

The standard spring.datasource.* properties are not relevant for R2DBC.
Örnek - mysql
Şöyle yaparız
spring.r2dbc.url=r2dbc:pool:mysql://localhost:3306/SQL_RX_TEST?
  zeroDateTimeBehavior=convertToNull&useSSL=false&useServerPrepareStatement=true
Örnek  postgre
Şöyle yaparız
spring.r2dbc.url=r2dbc:postgresql://postgres@localhost:5432/reactive
logging.level.org.springframework.r2dbc=DEBUG
Örnek - loglama
Şöyle yaparız
# I can enable logging using application.properties to see the executed queries— handy.

logging.level.org.springframework.data.repository=DEBUG
logging.level.org.springframework.r2dbc.core=DEBUG


SpringData R2DBC R2dbcRepository

Örnek
Şöyle yaparız
public interface MemberRepository extends R2dbcRepository<Member, Long> {
  Mono<Member> findByName(String name);
}

27 Ocak 2021 Çarşamba

SpringCloud Netflix Ribbon @LoadBalanced Anotasyonu

Giriş
Şu satırı dahil ederiz
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
Eureka Server'ı kullanarak Service Discovery yapar ve rest çağrısı yapar.  Açıklaması şöyle
@LoadBalanced is a marker annotation. It is used to indicate that RestTemplate or WebClient should use a RibbonLoadBalancerClient or LoadBalancerClient for interacting with the services. In turn, this allows you to use “logical identifiers” for the URLs we pass to the RestTemplate or WebClient. These logical identifiers are typically the name of a service registered in service discovery.

Note:- @RibbonClient(spring-cloud-starter-netflix-ribbon) and @LoadBalancerClient(spring-cloud-commons) are optional. This both works as a load-balancer on the client-side and gives us control over the HTTP and TCP clients. If we are using Service Discovery then we are good to go with default settings. we don't need to explicitly use these annotations.
Örnek
Açıklaması şöyle
Used as a marker annotation indicating that the annotated RestTemplate should use a RibbonLoadBalancerClient for interacting with your service(s).

In turn, this allows you to use "logical identifiers" for the URLs you pass to the RestTemplate. These logical identifiers are typically the name of a service. 
Şöyle yaparız. Burada some-service-name Eureka'da tanımlı logical identifier
restTemplate.getForObject("http://some-service-name/user/{id}", String.class, 1);
Örnek
Şöyle yaparız
@LoadBalanced
@Bean
RestTemplate getRestTemplate() {
  return new RestTemplate();
}
name Alanı
Açıklaması şöyle
... if we are not using any service discovery or need to customize the settings of Ribbon or LoadBalancer for a particular client then we need to use any of the two.

name - the service we are calling Ribbon or LoadBlancer but need additional customizations for how Ribbon or LoadBlancer interacts with that service.

configuration - set it to an @Configuration class with all of our customizations defined as @Beans .
Şöyle yaparız
@Configuration
@LoadBalancerClient (name ="custom-config", configuration = SomeCustomConfiguration.class)
// @RibbonClient (name = "custom-config", configuration = SomeCustomConfiguration.class)
public class WebClientConfigurationLB {

}

SpringAOP Kullanımı

Giriş
SpringAOP Kavramları yazısına bakabilirsiniz.

Maven
Şu satırı dahil ederiz.
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Spring ile Aspect Oriented Programlama Yapmak için iki seçenek var.
1. Spring AOP
2. AspectJ. AspectJ varsayılan yöntem değil, çünkü yavaş. Açıklaması şöyle.
AspectJ is a runtime resolution, which has a performance drawback.
AspectJ için şu satırı dahil ederiz
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
</dependency>
Spring AOP
SpringAOP sadece bean'ler üzerinde kullanılabilir. Açıklaması şöyle.
Spring AOP runs everything through proxies which sadly can't be everywhere.
....
Proxies are created only for singletons (@Bean) so we are greatly limited when we want to secure methods on specific objects (such as JPA @Entities) that are not beans. Proxies also won't be called within calling objects (bean calling its methods in context of self - this).
Kullanım
1. Bir @Aspect sınıfı oluşturulur. Bu sınıfın Spring ayağa kalkarken bulunuyor olması gerekir.
2. Bu sınıf içinde @Pointcut ile hangi metodların seçileceği belirtilir.
3. Daha sonra Advice ile Pointcut birbirlerine sağlanır. Temel olarak 3 çeşit advice vardır. Bunlar şöyle
- @Before Advice 
- @After Advice
- @Around Advice

@AfterThrowing de bir advice'tır ancak temel kullanıma dahil etmedim.

@Before ile @Pointcut ile seçilen metodlardan önce kod çalıştırılabilir. @Before Anotasyonu yazısına bakabilirsiniz.

@After ile @Pointcut ile seçilen metodlardan sonra kod çalıştırılabilir. @After Anotasyonu yazısına bakabilirsiniz.

@Around ile @Pointcut ile seçilen metodlardan önce ve sonra kod çalıştırılabilir. 

6. @AfterThrowing ile @Pointcut ile seçilen metodlardan exception fırlatılınca kod çalıştırılabilir. 

Örnek
Şöyle yaparız. Burada özel bir @Pointcut tanımlanmıyor. @Before advice içine @LogMethod anotasyonuna sahip metodlar tanımlanıyor.
@Aspect
@Component
public class LoggingAspect {

  @Before("@annotation(LogMethod)")
  public void logMethodName(JoinPoint joinPoint) {
    String method = joinPoint.getSignature().getName();
    String params = Arrays.toString(joinPoint.getArgs());
    System.out.println("Method [" + method + "] gets called with parameters " + params);
  }
}
Örnek
Şöyle yaparız. Burada 3 tane @Pointcut var ve metodların için boş. 
- Daha sonra @Before advice ile pointcut bağlanıyor
- @AfterThrowing advice ile pointcut bağlanıyor
@Aspect
public class LoggingAspect {

@Pointcut("execution(* com.tlc.tracker.v01.controller.*.*(..))")
public void controllerMethods() { }

@Pointcut("execution(* com.tlc.tracker.v01.service.imp.ProjectServiceImpl.*(..))")
public void serviceMethods(){  }

@Pointcut("execution(* com.tlc.tracker.v01.repository.*.*(..))")
public void respositoryMethods(){  }

@Before("serviceMethods()")
public void Areturn(JoinPoint point){
    System.out.println("Hola");
    log.info("HOLA");
}

@AfterThrowing(pointcut = "controllerMethods() && serviceMethods() 
  && respositoryMethods()", throwing = "exception")
public void logAfterThrowing(JoinPoint joinPoint, Throwable exception){

  if(exception instanceof BusinessServiceException){
    //Get the parts of the exception message
    String exceptions[] = exception.getMessage().split(":", 2);
    //Get the message contained in the exception message
    String message = exceptions.length == 1 ? "" : exceptions[1];

    log.error("[" + joinPoint.getSignature().getName() 
      + "] - Type: " + exceptions[0] + ". Message: "+ message);
  }

  if(exception instanceof Exception){
    log.error("[" + joinPoint.getSignature().getName()
      + "] - Type: ServerError. Message: "  + exception.getMessage());
  }
}