Cache manager with Spring Data Redis 2.0.3 - redis

Currently, I'm using spring cache by using #Cachable with Ehcache. I'm about to replace Ehcache with Redis by using Spring Data Redis 2.0.3. All examples I see on the web are based on the old versions of it but the new version has the different format of constructors.
This is my current cacheManager conf:
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml"/>
<property name="shared" value="true"/>
</bean>
The example of using Redis based on the older version is:
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"
c:template-ref="redisTemplate" />
The constructor in the new version is totally different from the older versions and all examples of the new version put everything into cache manually like this:
redisTemplate.opsForHash().put(user.getObjectKey(), user.getKey(), user);
I still want to use cacheManager and #cachable but have no idea how to configure the cacheManager bean with the new version of Spring Data Redis. The constructor of the new versions needs RedisCacheWriter:
public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration)
I appreciate if you can share your idea about how to set up cacheManager to use #Cachable with the new version of Spring Data Redis(min 2.0.3).

In Spring Data Redis 2.0+,you could write like this:
/**
* Redis config.
*
* #author chenxinyu
*/
#Configuration
#EnableCaching
public class RedisConfig {
#Value("${spring.redis.host}")
private String redisHost;
#Value("${spring.redis.port}")
private Integer redisPort;
#Bean
public JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration configuration =
new RedisStandaloneConfiguration(redisHost, redisPort);
return new JedisConnectionFactory(configuration);
}
#Bean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
RedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
RedisSerializer stringRedisSerializer = new StringRedisSerializer();
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
#Bean
public CacheManager initRedisCacheManager(RedisConnectionFactory factory) {
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager
.RedisCacheManagerBuilder.fromConnectionFactory(factory);
return builder.build();
}
}
this is just one example, you can see more methods in RedisCacheManager.java.

Related

RabbitTemplate: Broker does not support fast replies via 'amq.rabbitmq.reply-to'

I am using Spring boot (version 2.2.7.RELEASE hence spring rabbit 2.2.6) and RabbitMQ 3.3.5. why RabbitTemplate can not use direct-reply? is there any configuration with the broker?
spring rabbit config:
#Configuration
#EnableRabbit
public class RabbitConfig {
private String host = "localhost";
private int port = 5672;
private String username = "admin";
private String password = "admin";
private String exchangeName = "xxx";
private String queueName = "qqq";
#Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setHost(this.host);
factory.setPort(this.port);
factory.setUsername(this.username);
factory.setPassword(this.password);
return factory;
}
#Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory factory){
RabbitAdmin admin = new RabbitAdmin(factory);
return admin;
}
#Bean
public Queue queue(){
return new Queue(this.queueName, true);
}
#Bean
public Exchange exchange(){
return new DirectExchange(this.exchangeName, true, false);
}
#Bean
public Binding binding(Queue queue, Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with(queue.getName()).noargs();
}
#Bean
public RabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory cFactory){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(cFactory);
factory.setMessageConverter(messageConverter());
factory.setAcknowledgeMode(AcknowledgeMode.AUTO);
return factory;
}
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory factory){
RabbitTemplate rabbitTemplate = new RabbitTemplate(factory);
rabbitTemplate.setMessageConverter(messageConverter());
return rabbitTemplate;
}
#Bean
public MessageConverter messageConverter(){
//return new Converter();
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
jackson2JsonMessageConverter.getJavaTypeMapper().addTrustedPackages("com.mhr.xp.amqp", "java.lang");
return jackson2JsonMessageConverter;
}
}
here I use RabbitTemplate:
Object o = rabbitTemplate.convertSendAndReceive("xxx", "qqq", new Dto("ggg"));
I get the following warnning:
2020-05-15 23:06:59.188 WARN 15681 --- [ main] o.s.amqp.rabbit.core.RabbitTemplate : Broker does not support fast replies via 'amq.rabbitmq.reply-to', temporary queues will be used: channel error; protocol method: #method(reply-code=404, reply-text=NOT_FOUND - no queue 'amq.rabbitmq.reply-to' in vhost '/', class-id=50, method-id=10).
Direct reply-to was added to RabbitMQ in version 3.4.
The current version is 3.8.3.
https://www.rabbitmq.com/download.html
https://docs.spring.io/spring-amqp/docs/2.2.6.RELEASE/reference/html/#direct-reply-to
Starting with version 3.4.0, the RabbitMQ server supports direct reply-to. This eliminates the main reason for a fixed reply queue (to avoid the need to create a temporary queue for each request). Starting with Spring AMQP version 1.4.1 direct reply-to is used by default (if supported by the server) ...

NancyFX + .NET Core + NHibernate

I have created a small API based on NancyFx on .NET Core 2. It uses AutoFac as the IOC container and NHibernate 5.3 to access the database.
I have run into a problem with threading and the CurrentSessionContext. Basically when I enter the AfterRequest pipeline, I am usually on another thread, and then the CurrentSessionContext doesn't know about the binding I did at the beginning of the request.
I have tried to use the WebSessionContext instead, but since I am using the stack I am, there is no HttpContext.Current. To get access to the HttpContext you have to inject the Microsoft.AspNetCore.Http.IHttpContextAccessor where you need it.
How can I tell NHibernate to bind to my own context somehow, so I my session isn't lost between BeforeRequest and AfterRequest?
To make it easy to wrap my data accecss in a transaction I have added the following to my Nancy Bootstrapper:
protected override void ApplicationStartup(ILifetimeScope container, IPipelines pipelines)
{
base.ApplicationStartup(container, pipelines);
ConfigureNHibernateSessionPerRequest(container, pipelines);
}
private void ConfigureNHibernateSessionPerRequest(ILifetimeScope container, IPipelines pipelines)
{
pipelines.BeforeRequest += ctx => CreateSession(container);
pipelines.AfterRequest += ctx => CommitSession(container);
pipelines.OnError += (ctx, ex) => RollbackSession(container);
}
private Response CreateSession(ILifetimeScope container)
{
var provider = container.Resolve<INHibernaterSessionFactoryProvider>();
var sessionFactory = provider.Factory;
var requestSession = sessionFactory.OpenSession();
CurrentSessionContext.Bind(requestSession);
requestSession.BeginTransaction();
return null;
}
private AfterPipeline CommitSession(ILifetimeScope container)
{
var provider = container.Resolve<INHibernaterSessionFactoryProvider>();
var sessionFactory = provider.Factory;
if (CurrentSessionContext.HasBind(sessionFactory))
{
var requestSession = sessionFactory.GetCurrentSession();
requestSession.Transaction.Commit();
CurrentSessionContext.Unbind(sessionFactory);
requestSession.Dispose();
}
return null;
}
private Response RollbackSession(ILifetimeScope container)
{
var provider = container.Resolve<INHibernaterSessionFactoryProvider>();
var sessionFactory = provider.Factory;
if (CurrentSessionContext.HasBind(sessionFactory))
{
var requestSession = sessionFactory.GetCurrentSession();
requestSession.Transaction.Rollback();
CurrentSessionContext.Unbind(sessionFactory);
requestSession.Dispose();
}
return null;
}
My hibernate.cfg.xml looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="dialect">NHibernate.Dialect.MsSql2012Dialect</property>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="connection.connection_string">...</property>
<property name="show_sql">true</property>
<property name ="current_session_context_class">thread_static</property>
</session-factory>
</hibernate-configuration>
And I wire up the Sessionfactory like this:
var configuration = new Configuration();
configuration.Configure();
configuration.AddAssembly(Assembly.GetExecutingAssembly());
_factory = configuration.BuildSessionFactory();
You can try using AspNetCore middleware since that will give you access to HttpContext.
I found a solution that works, but isn't really as nice as I would have liked it to be.
In my Nancy Bootstrapper I have added a public static property that I populate from the ApplicationStartup hook:
public class Bootstrapper : AutofacNancyBootstrapper
{
public static IHttpContextAccessor HttpContextAccessor { get; private set; }
protected override void ApplicationStartup(ILifetimeScope container, IPipelines pipelines)
{
HttpContextAccessor = container.Resolve<IHttpContextAccessor>();
}
}
Then I have created a new custom CurrentSessionContext that I just called CoreSessionContext. It extends the abstract MapBasedSessionContext just like the WebSessionContext does, and then I inject the HttpContextAccessor in the constructor by accessing the static property on the Bootstrapper.
public class CoreSessionContext : MapBasedSessionContext
{
private IHttpContextAccessor _httpContextAccessor;
private const string SessionFactoryMapKey = "NHibernate.Context.WebSessionContext.SessionFactoryMapKey";
public CoreSessionContext(ISessionFactoryImplementor factory) : base(factory)
{
_httpContextAccessor = Bootstrapper.HttpContextAccessor;
}
protected override IDictionary GetMap()
{
return _httpContextAccessor.HttpContext.Items[SessionFactoryMapKey] as IDictionary;
}
protected override void SetMap(IDictionary value)
{
_httpContextAccessor.HttpContext.Items[SessionFactoryMapKey] = value;
}
}
The last thing I did was to remove the current_session_context_class element from the hibernate.cfg.xml file and then wire up the SessionFactory with my custom session context like this in line three:
var configuration = new Configuration();
configuration.Configure();
configuration.CurrentSessionContext<CoreSessionContext>();
configuration.AddAssembly(Assembly.GetExecutingAssembly());
_factory = configuration.BuildSessionFactory();
Then I am able to use the HttpContext provided by AspNetCore.
Not as pretty as I would like it to be, but it works.

How to Achieve Concurrency With a Non-Thread-Safe MessageListener

The answer to this question explains how to use prototype scope with <rabbit:listener-container/> in Spring AMQP when the listener is not thread-safe.
Another user asked (in a comment) how to configure the same environment using only Java Configuration.
It's generally best practice to use stateless beans for listeners but when that's not possible, to configure #Prototype scope listener (and multiple containers) using only Java Configuration, you can use:
#Bean
public SimpleMessageListenerContainer container1() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());
container.setQueueNames("test.mismatch");
container.setMessageListener(new MessageListenerAdapter(listener()));
container.setMismatchedQueuesFatal(true);
return container;
}
...
#Bean
public SimpleMessageListenerContainer containerN() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());
container.setQueueNames("test.mismatch");
container.setMessageListener(new MessageListenerAdapter(listener()));
container.setMismatchedQueuesFatal(true);
return container;
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MyNotThreadSafeListener listener() {
return new MyNotThreadSafeListener();
}
Remember that any dependencies injected into MyNotThreadSafeListener must also be prototype beans.
Bottom line is stateless beans are best.

Use my own rabbitTemplate with auto config connection factory Spring boot weird behaviour

Hi I was trying to define my own rabbitTemplate with autoconfig connctionFactory of spring boot .
first i tried this :
#Configuration
#EnableRabbit
public class MyConfiguration {
#Autowired
private ConnectionFactory connectionFactory;
#Bean
public RabbitTemplate rabbitTemplate(){
RabbitTemplate template = new RabbitTemplate(connectionFactory);
return template;
}
This was not working ...
So I have tried this :
#Configuration
#EnableRabbit
public class MyConfiguration {
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
}
And its works perfectly can someone explain this ?
The problem is that you're injecting a bean reference in your configuration class. That bean reference is something that is provided by auto-config. We can't process that configuration class until those field references are wired. But, we're supposed to "parse" user configuration first (i.e. before auto-configuration kicks in).
This has nothing to do with Spring Boot by the way. You should avoid injecting dependencies like that, especially if these are only required by one #Bean declaration.
In other words, try as much as possible to avoid cycles in your configuration.

How to send & consume Object in Spring AMQP?

I want to send and consume the custom object as below using Spring AMQP.
Producer code
Record record = new Record("message1", new Date());
rabbitTemplate.convertAndSend(record);
Can anyone provide spring amqp #configuration settings for sending and consuming messages as above. Thanks!!!
You should take a look at the Sample Applications; some of them use #Configuration.
But, essentially, you need...
#Bean
public SimpleMessageListenerContainer container() {
SimpleMessageListenerContainer container =
new SimpleMessageListenerContainer(connectionFactory());
MessageListenerAdapter adapter = new MessageListenerAdapter(myListener());
container.setMessageListener(adapter);
container.setQueues(foo());
return container;
}
#Bean
public Object myListener() {
return new Foo();
}
and the listener can be a POJO...
public class Foo {
public void handleMessage(Record foo) {
System.out.println(foo);
}
}
EDIT:
I added a Gist here for XML version.