29 Eylül 2023 Cuma

SpringScheduling Shedlock JdbcTemplateLockProvider Sınıfı

Giriş
Şu satırı dahil ederiz
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
constructor
JdbcTemplateLockProvider.Configuration .builder()...build() şeklinde kullanılır


JdbcTemplateLockProvider.Configuration.Builder Sınıfı
Bu sınıf hemen hemen her yerde aynı şekilde kullanılıyor

withJdbcTemplate metodu
Örnek
Şöyle yaparız
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;

@Configuration
public class ShedLockConfig {

  @Bean
  public LockProvider lockProvider(DataSource dataSource) {
    return new JdbcTemplateLockProvider(JdbcTemplateLockProvider.Configuration
      .builder()
      .withJdbcTemplate(new JdbcTemplate(dataSource))
      .usingDbTime() // Use database time for locks
      .build());
  }
}
Örnek
Şöyle yaparız
@Bean
@SuppressWarnings("unused")
public LockProvider lockProvider(DataSource dataSource) {
  return new JdbcTemplateLockProvider(
    JdbcTemplateLockProvider.Configuration.builder()
      .withJdbcTemplate(new JdbcTemplate(datasource))
      .usingDbTime ()
      .build ()
  );
}
Örnek
Şöyle yaparız
@Bean
public LockProvider lockProvider(DataSource dataSource) {  
  return new JdbcTemplateLockProvider(  
    JdbcTemplateLockProvider.Configuration.builder()  
      .withJdbcTemplate(new JdbcTemplate(dataSource))  
      .usingDbTime() // Works on Postgres, MySQL, MariaDb, MS SQL, Oracle, DB2, HSQL, H2  
      .build()  
  );  
}

SpringQuartz @DisallowConcurrentExecution Anotasyonu

Giriş
Şu satırı dahil ederiz
import  org.quartz.DisallowConcurrentExecution;
Örnek
Şöyle yaparız
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

@DisallowConcurrentExecution
public class MyQuartzJob implements Job {
  @Override
  public void execute(JobExecutionContext context) 
  throws JobExecutionException {
    // Your job logic here
  }
}
Açıklaması şöyle
Quartz provides built-in support for locking jobs to prevent concurrent executions. You can set the @DisallowConcurrentExecution annotation on your job classes to ensure that only one instance of a job runs at a time

By adding @DisallowConcurrentExecution, Quartz will automatically ensure that a job instance is locked while it's running, preventing concurrent executions of the same job.


Örnek
Açıklaması şöyle
In the SampleCronJob I have used a annotation @DisallowConcurrentExecution once this is added to a job and if we have multiple scheduler instance are running concurrently this job will not be executed by multiple schedulers concurrently.
Şöyle yaparız
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

@DisallowConcurrentExecution
public class SampleCronJob extends QuartzJobBean {
  @Override
  protected void executeInternal(JobExecutionContext context)
throws JobExecutionException {
    ...
  }
}

SpringQuartz SchedulerFactoryBean Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
Örnek
Şöyle yaparız
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

@Configuration
public class QuartzConfig {
  @Bean
  public SchedulerFactoryBean schedulerFactoryBean() {
    SchedulerFactoryBean factory = new SchedulerFactoryBean();
    factory.setJobFactory(new SpringBeanJobFactory());
    factory.setDataSource(dataSource); // Inject your data source here
    factory.setQuartzProperties(quartzProperties());
    factory.setOverwriteExistingJobs(true);
    factory.setWaitForJobsToCompleteOnShutdown(true);
    return factory;
  }

  // Configure Quartz properties (e.g., thread count, clustering, etc.)
  private Properties quartzProperties() {
    Properties properties = new Properties();
    properties.setProperty("org.quartz.scheduler.instanceName", "MyScheduler");
    properties.setProperty("org.quartz.scheduler.instanceId", "AUTO");
    // Set the number of worker threads
    properties.setProperty("org.quartz.threadPool.threadCount", "5"); 
    properties.setProperty("org.quartz.jobStore.isClustered", "true");
    // Interval for cluster node check-in
    properties.setProperty("org.quartz.jobStore.clusterCheckinInterval", "2000"); 
    properties.setProperty("org.quartz.jobStore.class",
      "org.quartz.impl.jdbcjobstore.JobStoreTX");
    properties.setProperty("org.quartz.jobStore.driverDelegateClass",
      "org.quartz.impl.jdbcjobstore.StdJDBCDelegate");
    properties.setProperty("org.quartz.jobStore.tablePrefix", "QRTZ_");
    return properties;
  }
}


18 Eylül 2023 Pazartesi

SpringMVC MethodArgumentNotValidException Sınıfı

Giriş
Şu satırı dahil ederiz
import  org.springframework.web.bind.MethodArgumentNotValidException;
Exception döndürmek için 3 yöntem var
- @ControllerAdvice + @ExceptionHandler : Global Exception handling yapar
- HandlerExceptionResolver
- ResponseStatusException
Kullanım
MethodArgumentNotValidException sınıfının 
1. getBindingResult() metodu çağrılır ve bir BindingResult nesnesi elde edilir. 
2. Bu nesnenin getAllErrors() metodu kullanılarak ObjectError nesneleri dolaşılır. 
3. ObjectError nesnesinin getDefaultMessage() metodu ile hata mesajı alınır. getRejectedValue() value ile kabul edilmeyen değer alınabilir.

@ControllerAdvice
Örnek
Şöyle yaparız.
@ControllerAdvice
public class BillingExceptionHandler extends ResponseEntityExceptionHandler {

@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException arguments,
  HttpHeaders headers,
  HttpStatus status,
  WebRequest request) {

  BindingResult bindingResult = arguments.getBindingResult();
  List<String> validationMessages = new ArrayList<>();
  List<ObjectError> objectErrors = bindingResult.getAllErrors();
  for (ObjectError objectError : objectErrors) {
    String defaultMessage = objectError.getDefaultMessage();
    validationMessages.add(defaultMessage);
  }
  return new ResponseEntity<>(validationMessages, HttpStatus.BAD_REQUEST);
}
}
Örnek - Map<String,Object> 
Şöyle yaparız
@ControllerAdvice
public class GlobalExceptionHandler {

  @ExceptionHandler(value = MethodArgumentNotValidException.class) // 1.
  public ResponseEntity<Map<String,Object>>handleException(
    HttpServletRequest request, MethodArgumentNotValidException e) {

    var fieldErrors = e.getFieldErrors();

    List<Map<String,String>> fieldErrorsMap = fieldErrors.stream() // 2.
      .map(fieldError -> {
        var fieldErrorObject = new Object() {
        final String fieldName = fieldError.getField();
        final String errorMessage = fieldError.getDefaultMessage();
      };
      return Map.ofEntries(
        Map.entry("field", fieldErrorObject.fieldName),
        Map.entry("message", fieldErrorObject.errorMessage != null
                                    ? fieldErrorObject.errorMessage
                                    : ""));})
      .collect(Collectors.toList());

      Map<String,Object> errorResponsePayload =
        Map.of("path",request.getServletPath(), "errors",fieldErrorsMap); // 3.

      return ResponseEntity.badRequest().body(errorResponsePayload);
  }
}
@RestControllerAdvice
Aslında @ControllerAdvice ile aynı şey.
Örnek - Map<String, String>
Şöyle yaparız.
@RestControllerAdvice
public class CustomExceptionHandler {

  @ExceptionHandler(MethodArgumentNotValidException.class)
  public ResponseEntity<Object> handleValidationExceptions(
    MethodArgumentNotValidException ex) {
    Map<String, String> errors = new HashMap<>();
    ex.getBindingResult().getAllErrors().forEach((error) -> {
      String fieldName = ((FieldError) error).getField();
      String errorMessage = error.getDefaultMessage();
      errors.put(fieldName, errorMessage);
    });
    return ResponseEntity.badRequest().body(errors);
  }
}
@ExceptionHandler
Açıklaması şöyle
1. @ExceptionHandler Instructs Spring which exception type should handle.
2. We’re extracting all the invalid field errors, they contain the validation metadata for each validation that failed for a specific bean attribute.
3. Creating a map that will be returned as a JSON format containing the endpoint path and an array with the field errors.
Örnek - Map<String, String>
ResponseEntityExceptionHandler sınıfını kullanmak zorunda değiliz. Sadece bir RestController içinde özel bir şey yapmak istersek RestController sınıfına şöyle bir metod ekleriz. MethodArgumentNotValidException sınıfının getBindingResult() metodu çağrılır ve bir BindingResult nesnesi elde edilir. Bu nesnenin getAllErrors() metodu kullanılarak ObjectError nesneleri dolaşılır. Ancak bu sefer ObjectError nesnesini FieldError tipine cast ediyoruz. Böylece FieldError nesnesinin getField() metodunu kullanarak hatalı alanın ismine de erişebiliriz.
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {

  Map<String, String> errors = new HashMap<>();
  ex.getBindingResult().getAllErrors().forEach((error) -> {
    String fieldName = ((FieldError) error).getField();
    String errorMessage = error.getDefaultMessage();
    errors.put(fieldName, errorMessage);
  });
  return errors;
}

Örnek
Şöyle yaparız. Bu örnek diğerlerinden farklı olarak hangi değerin kabul edilmediğini de gösterir
public class ApiValidationError {
    private String field;
    private String message;
    private Object rejectedValue;

    // Constructors, Getters, Setters
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<List<ApiValidationError>> handleDetailedValidationExceptions(
  MethodArgumentNotValidException ex) {
  List<ApiValidationError> errors = ex.getBindingResult()
    .getFieldErrors()
    .stream()
    .map(err -> new ApiValidationError(err.getField(), err.getDefaultMessage(),
      err.getRejectedValue()))
    .collect(Collectors.toList());
    
  return ResponseEntity.badRequest().body(errors);
}

SpringCloud Contract WireMock

Giriş
SpringCloud Contract WireMock kullanmaya gerek var mı bilmiyor. Çünkü karışık. Bunun yerine Java koduyla test REST uçları açmak daha kolay olabilir

Maven
Şu satırı dahil ederiz
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-contract-wiremock</artifactId>
   <version>4.0.4</version>
</dependency>
Örnek
Şöyle yaparız
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;

@SpringBootApplication
@AutoConfigureWireMock()
public class SpringStubServerApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringStubServerApplication.class, args);
  }
}
Açıklaması şöyle
We need to add the AutoConfigureWireMock annotation to start a WireMock server in the context of the Spring application. This will bind the port, HTTPS port, and stub files and locations of the WireMock server when the Spring Boot application is started.
application.yaml şöyledir
spring:
  application:
    name: wiremock-service
  main:
    web-application-type: none

wiremock:
  server:
    files: classpath:/__files
    stubs: classpath:/mappings

logging:
  level:
    org.springframework.cloud.contract.wiremock: debug
    org.springframework.web.client: debug
    com.github.tomakehurst.wiremock: trace
files ile cevap olarak gönderilecek JSON dosyaları tanımlanır
stubs ile REST noktaları tanımlanır

 


15 Eylül 2023 Cuma

SpringKafka Consumer @KafkaListener Anotasyonu groupId Alanı - Consumer Group

Giriş
Consumer Group ismini belirtir

Örnek
Şöyle yaparız. Burada farklı consumer group oluşturularak aynı mesajın iki farklı listener'a gitmesi sağlanıyor
//THE CODE READY TO COPY, LAUNCH AND TEST
@Service
public class KafkaScheduler {

    @Autowired
    private  KafkaTemplate<String, String> kafkaTemplate;

    @Scheduled(cron = "*/10 * * * * *")
    public void send() {
        kafkaTemplate.send("topic-one", "kafkaMessage " + new Date());
        System.out.println("MESSAGE WAS SENT");
    }

    @KafkaListener(id = "id1",
            groupId = "group-one",
            topics = "topic-one")
    public void listenServiceCall(@Payload String message) {
        System.out.println("GROUP ONE MESSAGE " + message);
    }

    @KafkaListener(id = "id2",
            groupId = "group-two",
            topics = "topic-one")
    public void listenServiceCall2(@Payload String message) {
        System.out.println("GROUP TWO MESSAGE " + message);
    }
}

7 Eylül 2023 Perşembe

SpringContext @Qualifier Anotasyonu - Custom Annotation

Giriş
Açıklaması şöyle
While @Qualifier is helpful, using string identifiers can be error-prone. To avoid this, custom qualifiers come into play. These are annotations you create, annotated with @Qualifier. This gives you the advantage of compile-time checking, thereby reducing the possibility of errors.
Örnek
Elimizde iki tane anotasyon olsun. Burada SportsVehicle ve LuxuryVehicle diye iki tane custom annotation tanımlanıyor. 
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
  ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier
public @interface SportsVehicle {
}

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
  ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier
public @interface LuxuryVehicle {
}
Şöyle yaparız. Daha sonra her bean bunlardan hangisini kullanacaksa onunla işaretleniyor. @Autowired ile de hangi bean tipini istediğimizi belirtiyoruz
@Component
@SportsVehicle
public class Bike implements Vehicle {
 // …
}

@Component
@LuxuryVehicle
public class Car implements Vehicle {
 // …
}

@Autowired
@SportsVehicle
private Vehicle vehicle;


@Autowired
@LuxuryVehicle
private Vehicle vehicle;
Örnek
Elimizde kendi anotasyonumuz olsun. Bu anotasyonda @Qualifier kullanılıyor.
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE,
 ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Foo {
}
Bu anotasyonu kullanan bir metodumuz olsun.
@Foo
public IFooService service1() { return new SpecialFooServiceImpl(); }
Ya da bu anotasyonu direkt bean üzerinde kullanalım.
@Foo
@Component
public class EvenMoreSpecialFooServiceImpl { ... }
Bu anotasyonlara sahip bean'lere erişmek için şöyle yaparız. Burada List<Object> kullanılmasının sebebi bean'lerimiz arasında ortak bir ata sınıf olmaması
@Autowired
@Foo
List<Object> fooBeans; 
Eğer belli bir ata sınıftan kalıtan ve @Foo anotasyonuna sahip bean'leri istersek şöyle yaparız.
@Autowired
@Foo
List<SpecialFooServiceImpl> fooBeans;

SpringIntegration Splitter

Giriş
Açıklaması şöyle
A Splitter takes a single message and splits it into multiple messages based on specific criteria.
Örnek
Şöyle yaparız
@Configuration
public class SplitterConfig {

    @Bean
    public MessageChannel inputChannel() {
        return new DirectChannel();
    }

    @Bean
    public MessageChannel outputChannel() {
        return new DirectChannel();
    }

    @Bean
    @Splitter(inputChannel = "inputChannel", outputChannel = "outputChannel")
    public AbstractMessageSplitter splitter() {
        return new AbstractMessageSplitter() {
            @Override
            protected List<?> splitMessage(Message<?> message) {
                return Arrays.asList(message.getPayload().toString().split(","));
            }
        };
    }
}
Açıklaması şöyle
In this configuration, we define two DirectChannel beans: inputChannel and outputChannel. We also define a Splitter that splits the message payload into multiple messages based on the comma delimiter and sends them to the outputChannel.

SpringIntegration Aggregator

Giriş
Açıklaması şöyle
An Aggregator collects and combines messages that share a common correlation before sending them as a single message.
Örnek
Şöyle yaparız
@Configuration
public class AggregatorConfig {

    @Bean
    public MessageChannel inputChannel() {
        return new DirectChannel();
    }

    @Bean
    public MessageChannel outputChannel() {
        return new DirectChannel();
    }

    @Bean
    @Transformer(inputChannel = "inputChannel", outputChannel = "aggregatorChannel")
    public HeaderEnricher correlationHeaderEnricher() {
        Map<String, Expression> headersToAdd = new HashMap<>();
        headersToAdd.put("correlationId", new ValueExpression<>("aggregatedPayload"));
        return new HeaderEnricher(headersToAdd);
    }

    @Bean
    public PollableChannel aggregatorChannel() {
        return new QueueChannel();
    }

    @Bean
    @Aggregator(inputChannel = "aggregatorChannel", outputChannel = "outputChannel")
    public DefaultAggregatingMessageGroupProcessor aggregator() {
        return new DefaultAggregatingMessageGroupProcessor();
    }
}
Açıklaması şöyle
In this configuration, we define two DirectChannel beans: inputChannel and outputChannel. We use a Transformer to enrich the message headers with a correlationId. The Aggregator combines messages with the same correlationId and sends the aggregated message to the outputChannel.
Örn

SpringIntegration Content-Based Router

Örnek
Şöyle yaparız
@Configuration
public class ContentBasedRoutingConfig {

  @Bean
  public MessageChannel inputChannel() {
    return new DirectChannel();
  }

  @Bean
  public MessageChannel evenChannel() {
    return new DirectChannel();
  }

  @Bean
  public MessageChannel oddChannel() {
    return new DirectChannel();
  }

  @Bean
  Router(inputChannel = "inputChannel")
  public ExpressionEvaluatingRouter router() {
    SpelExpressionParser parser = new SpelExpressionParser();
    Expression expression = parser.parseExpression("payload % 2 == 0 ? 
      'evenChannel' : 'oddChannel'");
    return new ExpressionEvaluatingRouter(expression);
  }
}

SpringIntegration Message Channel (Point-to-Point)

Örnek
Şöyle yaparız
@Configuration
public class PointToPointConfig {

  @Bean
  public MessageChannel inputChannel() {
    return new DirectChannel();
  }

  @Bean
  public MessageChannel outputChannel() {
    return new DirectChannel();
  }

  @Bean
  @ServiceActivator(inputChannel = "inputChannel", outputChannel = "outputChannel")
  public Transformer uppercaseTransformer() {
    return new AbstractTransformer() {
      @Override
      protected Object doTransform(Message<?> message) {
        return message.getPayload().toString().toUpperCase();
      }
    };
  }
}

@Value Anotasyonu - Ortam Değişkeni Okuma

Örnek
Şöyle yaparız
@Value("${env.SOME_ENV_VARIABLE:defaultValue}")
private String someEnvVariable;

SpringContext @Value Anotasyonu - Map Okuma

Örnek
application.properties dosyası şöyle olsun.
app.mappings={key1:'value1', key2:'value2'}
Şöyle yaparız.
@Value("#{'${app.mappings}")
private Map<String,String> mappings;
Örnek
application.properties dosyası şöyle olsun.
employee.age={one:'26', two : '34', three : '32', four: '25'}
Şöyle yaparız.
@Value("#{'${employee.age}")
private Map<String,Integer> employeeName;
Belli bir key değerine erişmek için şöyle yaparız.
@Value("#{'${employee.age}.two")
private String employeeName;
Belli bir value değerine erişmek için şöyle yaparız. Eğer value yoksa varsayılan değer olarak 30 gelir.
@Value("#{'${employee.age}.['two'] ?: 30")
private Integer employeeAge;