16 Mayıs 2020 Cumartesi

SpringTest @ContextConfiguration Anotasyonu - IntegrationTest İçindir

Giriş
Şu satırı dahil ederiz.
import org.springframework.test.context.ContextConfiguration;
Not : Bu anotasyon yerine daha gelişmiş olan @SpringJUnitConfig tercih edilebilir

Bu sınıf  spring dokümantasyonunda Integration Test için kullanılır deniliyor. Çünkü gerçek bir ApplicationContext ayağa kaldırılıyor. Testte @Autowired ApplicationContext yaparsak bu nesnenin dolu geldiğini görebiliriz. Böylece artık beanleri test etmek için  @MockBean,  @SpyBean kullanmaya gerek kalmıyor

Açıklaması şöyle
@ContextConfiguration annotation defines a class level metadata that is used to determine how to load and configure the ApplicationContext. @ContextConfiguration declares the application context resource locations or annotated classes used to load the context.
Yani bu anotasyon 
1. classes alanına verilen bean'leri ApplicationContext'e ekliyor. 
2. locations alanında verilen XMl'deki bean'leri ApplicationContext'e ekliyor.  
3. initializers alanına verilen bean'leri ApplicationContext'ten önce ilklendiriyor.

Eğer test kodunun bulunduğu src paketinde ikinci bir bean'i classes alanına yazmamışsam, otomatik olarak yüklenmiyor. @SpringBootTest ile karşılattırınca Integration Test içindir demek içimden gelmiyor :)

Açıklaması şöyle
If you are familiar with the Spring Test Framework, you may be used to using @ContextConfiguration(classes=…​) in order to specify which Spring @Configuration to load. Alternatively, you might have often used nested @Configuration classes within your test.

classes Alanı
Bu alanda src paketindeki gerçek bean'ler teker teker verilebilir veya @Configuration anotasyonuna sahip bean'ler verilebilir. İkinci kullanım daha pratik olabilir.

Örnek - Tek Bean
Şöyle yaparız.
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = MyBean.class)
class MyBeanTest {

  @MockBean
  EntityManager entityManager;

  @Autowired
  MyBean mybean;


  @Test
  void saveTest(){
    mybean.save();
    Mockito.verify(entityManager,Mockito.atLeastOnce()).merge.(Mockito.any());
  }
}
Örnek - @Configuration Anotasyonuna Sahip Bean
Elimizde şöyle bir kod olsun.
@Configuration
@ComponentScan(basePackages = "com.test.spring")
public class TestApplication {
}
TestApplication içinde bir bean'i test etmek için şöyle yaparız
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestApplication.class)
public class Test {
 ...
}
Örnek - - @Configuration Anotasyonuna Sahip Bean
Şöyle yaparız.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CacheableTest.CacheConfigurations.class)
public class CacheableTest {

  public static class Customer {
    ...
  }
  final public static AtomicInteger cacheableCalled = new AtomicInteger(0);
  final public static AtomicInteger cachePutCalled = new AtomicInteger(0);

  public static class CustomerCachedService {
    @Cacheable("CustomerCache")
    public Customer cacheable(String v) {
      cacheableCalled.incrementAndGet();
      return new Customer(v, "Cacheable " + v);
    }
    @CachePut("CustomerCache")
    public Customer cachePut(String b) {
      cachePutCalled.incrementAndGet();
      return new Customer(b, "Cache put " + b);
    }
  }
  @Configuration
  @EnableCaching()
  public static class CacheConfigurations {
    @Bean
    public CustomerCachedService customerCachedService() {
      return new CustomerCachedService();
    }
    @Bean
    public CacheManager cacheManager() {
      return new GuavaCacheManager("CustomerCache");
    }
  }
  @Autowired
  public CustomerCachedService cachedService;
  ...
}
Aynı sınıfta testleri şöyle yaparız.
@Test
public void testCacheable() {
  for(int i = 0; i < 1000; i++) {
    cachedService.cacheable("A");
  }
  Assert.assertEquals(cacheableCalled.get(), 1);
}

@Test
public void testCachePut() {
  for(int i = 0; i < 1000; i++) {
    cachedService.cachePut("B");
  }
  Assert.assertEquals(cachePutCalled.get(), 1000);
}
initializers Alanı
Spring context başlamadan önce çalışır. Test için kod çalıştırabilmeyi sağlar.
Örnek
Elimizde şöyle bir kod olsun. org.springframework.boot.test.util.TestPropertyValues
static class Initializer implements
  ApplicationContextInitializer<ConfigurableApplicationContext> {

  @Override
  public void initialize(ConfigurableApplicationContext configurableApplicationContext) {

    // initialize...
    TestPropertyValues
    //here you can add properties to connect...
    .of("some.property.key=some.property.value")
    .applyTo(configurableApplicationContext.getEnvironment());
  }
}
Şöyle yaparız.
@ContextConfiguration(initializers = Initializer.class)
locations Alanı
Yüklenecek bean'leri XML olarak belirtmeye yarar. value alanı ile aynıdır. String[] olarak tanımlanır.
Örnek
Şöyle yaparız.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext_test.xml" })
public class Test{
  @Autowired
  Foo foo;

  @Test
  public void checkOne(){
    System.out.println(foo.getBar());
  }
}

13 Mayıs 2020 Çarşamba

SpringBoot spring.rabbitmq Ayarları

Giriş
Açıklaması şöyle.
RabbitMq uses Advanced Message Queuing Protocol (AMQP).

In rabbitmq.conf the tcp port provided takes the port of the RabbitMq from your Java Application.

listeners.tcp.default = 5672

RabbitMQ Management console or the Web Admin uses the 15672 (default) port.
rabbitmq Ayarları
rabbitmq.host Alanı
Şöyle yaparız.
spring.rabbitmq.host=localhost
spring.rabbitmq.port=15672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
rabbitmq.virtual-host Alanı
Şöyle yaparız
spring.rabbitmq.addresses={rabbitmq_host}:5672
spring.rabbitmq.virtual-host: app_vhost
spring.rabbitmq.username=username
spring.rabbitmq.password=password
simple listener Ayarları
concurrency Alanları
Örnek
Şöyle yaparız
spring.rabbitmq.listener.simple.concurrency=4
spring.rabbitmq.listener.simple.max-concurrency=8
default-requeue-rejected Alanı
Örnek
Açıklaması şöyle. spring.rabbitmq.listener.simple.default-requeue-rejected alanının açıklaması şöyle
By default, all failed messages will be immediately requeued at the head of the target queue over and over again.
To change this behavior we have two options:

- Set the default-requeue-rejected option to false on the listener side – spring.rabbitmq.listener.simple.default-requeue-rejected=false
- Throw an AmqpRejectAndDontRequeueException – this might be useful for messages that won’t make sense in the future, so they can be discarded.
retry Alanları
Örnek
Açıklaması şöyle.
Here we enable the Spring Boot RabbitMQ retry mechanism and specify some more additional parameters:

Initial interval: The message should be retried after an interval of 3s.
Max-attempts: The message should be retried maximum of 6 times. After which it will be sent to dead letter Queue.
Max-interval: The maximum time interval between two retries should never exceed 10s.
Multiplier: The interval between second retry gets multiplied by 2. But this interval can never exceed the max-interval. So the retry interval values will be 3s, 6s, 10s, 10s, 10s. As 10 sec is the max interval specified.
Şöyle yaparız
spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true
          initial-interval: 3s
          max-attempts: 6
          max-interval: 10s
          multiplier: 2

server:
  port: 8081
Örnek
Şöyle yaparız
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

spring.rabbitmq.listener.simple.retry.enabled=true
// the first time will wait 5 seconds to try again
spring.rabbitmq.listener.simple.retry.initial-interval=5000 
//will try a maximum of 10 times
spring.rabbitmq.listener.simple.retry.max-attempts=10 
// the maximum interval between attempts is 5 minutes
spring.rabbitmq.listener.simple.retry.max-interval=300000 
// multiplies the range by 3
spring.rabbitmq.listener.simple.retry.multiplier=3.0 


SpringBoot Amqp RabbitMQ Kullanımı - FanoutExchange Sınıfı

Giriş
FanoutExchange kullanırken routing key önemsizdir. Tek yapmamız gereken FanoutExchange'e bir kuyruk bağlamak

Örnek
Şöyle yaparız
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class RabbitMQFanoutConfig {

  @Bean
  Queue marketingQueue() {
    return new Queue("marketingQueue", false);
  }

  @Bean
  FanoutExchange exchange() {
    return new FanoutExchange("fanout-exchange");
  }

  @Bean
  Binding marketingBinding(Queue marketingQueue, FanoutExchange exchange) {
    return BindingBuilder.bind(marketingQueue).to(exchange);
  }
}
Örnek
FanoutExchange yaratmak için şöyle yaparız
@Configuration
public class FanoutExchangeConfiguration {

    private static String fanoutExchange;

    @Value("${broker.exchange.fanout.name}")
    private void setFanoutExchange(String fanoutExchange) {
        FanoutExchangeConfiguration.fanoutExchange = fanoutExchange;
    }

    @Bean
    FanoutExchange fanoutExchange() {
        return new FanoutExchange(FanoutExchangeConfiguration.fanoutExchange);
    }
}
FanoutExchange'e kuyruk bağlamak için şöyle yaparız
@Configuration
public class BrokerConfiguration {

    static String shipExchangeQueue;
    static String shipRoutingKey;

    @Value("${broker.exchange.direct.ship.routing-key}")
    private void setShipRoutingKey(String routingKey) {
        BrokerConfiguration.shipRoutingKey = routingKey;
    }

    @Value("${broker.exchange.queue.name}")
    private void setExchangeQueue(String exchangeQueue) {
        BrokerConfiguration.shipExchangeQueue = exchangeQueue;
    }

    @Bean
    Queue queue() {
        return new Queue(BrokerConfiguration.shipExchangeQueue);
    }

  @Bean
  Binding bindingToDirectExchange(Queue commonQueue, DirectExchange directExchange) {
    return BindingBuilder.bind(commonQueue).to(directExchange)
.with(BrokerConfiguration.shipRoutingKey);
  }

  @Bean
  Binding bindingToFanoutExchange(Queue commonQueue, FanoutExchange fanoutExchange) {
    return BindingBuilder.bind(commonQueue).to(fanoutExchange);
  }
}
Örnek - FanoutExchange
Şöyle yaparız
@Configuration
public class FanoutConfigure {

  @Bean
  public Queue fanoutMessageA() {
    return new Queue("fanout.messageA");
  }
  @Bean
  public Queue fanoutMessageB() {
    return new Queue("fanout.messageB");
  }
  @Bean
  public Queue fanoutMessageC() {
    return new Queue("fanout.messageC");
  }

  @Bean 
  public FanoutExchange exchangeFanout() {/ / Declare a fanout switch
    return new FanoutExchange("fanoutExchange");
  }

  @Bean
  Binding bindingExchangeA(FanoutExchange fanoutExchange) {/ / Bind the queue to the switch
    return BindingBuilder.bind(fanoutMessageA()).to(fanoutExchange);
  }
  @Bean
  Binding bindingExchangeB(FanoutExchange fanoutExchange) {
    return BindingBuilder.bind(fanoutMessageB()).to(fanoutExchange);
  }
  @Bean
  Binding bindingExchangeC(FanoutExchange fanoutExchange) {
    return BindingBuilder.bind(fanoutMessageC()).to(fanoutExchange);
  }
}
Mesaj gönder göndermek için şöyle yaparız
@Component
public class FanoutSender {

  @Autowired
  RabbitTemplate rabbitTemplate;

  public void sendFanout() {
    String message = "...";
    // Leave "" because you do not need to specify routerkey"
    rabbitTemplate.convertAndSend("fanoutExchange", "", message);
  }
}

11 Mayıs 2020 Pazartesi

SpringJMS CachingConnectionFactory Sınıfı

Giriş
Şu satırı dahil ederizz
import org.springframework.jms.connection.CachingConnectionFactory;
Açıklaması şöyle. ConnectionFactory nesnelerini gerçekten cache'leyerek kullanılır
Although both the PooledConnectionFactory and the CachingConnectionFactory state that they each pool connections, sessions and producers, the PooledConnectionFactory does not actually create a cache of multiple producers. It simply uses a singleton pattern to hand out a single cached producer when one is requested. Whereas the CachingConnectionFactory actually creates a cache containing multiple producers and hands out one producer from the cache when one is requested.
Açıklaması şöyle
In order to connect and be able to send/receive messages, we need to configure a ConnectionFactory.

A ConnectionFactory is one of the JMS administered objects which are preconfigured by an administrator. A client with the help of the configuration will make the connection with a JMS provider.

Spring provides 2 types of ConnectionFactory:

- SingleConnectionFactory – is an implementation of ConnectionFactory interface, that will return the same connection on all createConnection() calls and ignore calls to close()
- CachingConnectionFactory – extends the functionality of the SingleConnectionFactory and adds enhances it with a caching of Sessions, MessageProducers, and MessageConsumers
destroy metodu
Şöyle yaparızz
@Bean(destroyMethod = "destroy")
public ConnectionFactory connectionFactory(JmsProperties appProperties) {
  ActiveMQConnectionFactory cf = ...

  return new CachingConnectionFactory(cf) {
    @Override
    public void destroy() {
      super.destroy();

      try {
        Thread.sleep(30000);
      } catch (InterruptedException e) {
        ...
      }
      System.out.println("CachingConnectionFactory is destroyed!");
    }
  };
}

SpringJMS DefaultJmsListenerContainer Sınıfı

Giriş
DefaultJmsListenerContainerFactory tarafından yaratılır. Mesajları asenkron olarak okur ve işler.

setConcurrency metodu
Açıklaması şöyle.
Notice the concurrency="10-50" property above. This is a simplified configuration for setting the concurrentConsumers=10 and the maxConcurrentConsumers=50 properties of the DMLC. This tells the DMLC to always start up a minimum of 10 consumers.
setConcurrentConsumers metodu
Açıklaması şöyle. Default olarak 1 tane consumer ile başlar. Bu metod yerine setConcurrency() metodu kullanılabilir.
The Spring DefaultMessageListenerContainer (DMLC) is a highly flexible container for consuming JMS messages that can handle many different use cases via the numerous properties that it provides. For the situation mentioned above, the DMLC offers the ability to dynamically scale the number of consumers. That is, as the number of messages available for consumption increases, the DMLC can automatically increase and decrease the number of consumers. To configure the DMLC to automatically scale the number message consumers, the concurrentConsumers property and the maxConcurrentConsumers property are used.
setTaskExecutor metodu
Açıklaması şöyle.
So, the main use-case for setting the task executor is for integration with an existing thread pool. The default executor will already scale according to the number of concurrent consumers you configure.